[EAGLE-1046] Eagle supports policies import to a new site from a policy prototype
https://issues.apache.org/jira/browse/EAGLE-1046
add prototype management page
support policy create with proto
Author: zombieJ <smith3816@gmail.com>
Author: Zhao, Qingwen <qingwzhao@apache.org>
Closes #963 from zombieJ/EAGLE-1046.
diff --git a/eagle-core/eagle-metadata/eagle-metadata-base/src/main/java/org/apache/eagle/metadata/resource/PolicyResource.java b/eagle-core/eagle-metadata/eagle-metadata-base/src/main/java/org/apache/eagle/metadata/resource/PolicyResource.java
index 13041cf..d09da4b 100644
--- a/eagle-core/eagle-metadata/eagle-metadata-base/src/main/java/org/apache/eagle/metadata/resource/PolicyResource.java
+++ b/eagle-core/eagle-metadata/eagle-metadata-base/src/main/java/org/apache/eagle/metadata/resource/PolicyResource.java
@@ -65,24 +65,25 @@
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public RESTResponse<PolicyEntity> saveAsPolicyProto(PolicyEntity policyEntity,
- @QueryParam("needPolicyCreated") boolean needPolicyCreated) {
+ @QueryParam("needPolicyProtoCreated") boolean needPolicyProtoCreated) {
return RESTResponse.async(() -> {
Preconditions.checkNotNull(policyEntity, "entity should not be null");
- Preconditions.checkNotNull(policyEntity, "policy definition should not be null");
+ Preconditions.checkNotNull(policyEntity.getDefinition(), "policy definition should not be null");
Preconditions.checkNotNull(policyEntity.getAlertPublishmentIds(), "alert publisher list should not be null");
PolicyDefinition policyDefinition = policyEntity.getDefinition();
- if (needPolicyCreated) {
- OpResult result = metadataResource.addPolicy(policyDefinition);
- if (result.code != 200) {
- throw new IllegalArgumentException(result.message);
- }
- result = metadataResource.addPublishmentsToPolicy(policyDefinition.getName(), policyEntity.getAlertPublishmentIds());
- if (result.code != 200) {
- throw new IllegalArgumentException(result.message);
- }
+ OpResult result = metadataResource.addPolicy(policyDefinition);
+ if (result.code != 200) {
+ throw new IllegalArgumentException(result.message);
}
- return importPolicyProto(policyEntity);
+ result = metadataResource.addPublishmentsToPolicy(policyDefinition.getName(), policyEntity.getAlertPublishmentIds());
+ if (result.code != 200) {
+ throw new IllegalArgumentException(result.message);
+ }
+ if (needPolicyProtoCreated) {
+ importPolicyProto(policyEntity);
+ }
+ return policyEntity;
}).get();
}
diff --git a/eagle-external/eagle-docker/resource/serf/bin/start-serf-agent.sh b/eagle-external/eagle-docker/resource/serf/bin/start-serf-agent.sh
index dbb8df0..035886a 100755
--- a/eagle-external/eagle-docker/resource/serf/bin/start-serf-agent.sh
+++ b/eagle-external/eagle-docker/resource/serf/bin/start-serf-agent.sh
@@ -1,5 +1,20 @@
#!/bin/bash
+# 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.
+
SERF_HOME=/usr/local/serf
SERF_BIN=$SERF_HOME/bin/serf
SERF_CONFIG_DIR=$SERF_HOME/etc
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html
index d5b3352..6ebb4f1 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html
@@ -49,6 +49,12 @@
</tbody>
</table>
</div>
+ <div class="box-footer text-right">
+ <button class="btn btn-primary" ng-click="makePrototype()">
+ <span class="fa fa-file-code-o"></span>
+ Make as Prototype
+ </button>
+ </div>
</div>
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html
index 1da0d3d..354d8d0 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html
@@ -220,7 +220,7 @@
</div>
<label>
- Publish Alerts
+ Alert Publishers *
</label>
<ul class="sm-padding">
@@ -252,6 +252,13 @@
<label>Schedule Parallelism *</label>
<input type="text" class="form-control" ng-model="policy.parallelismHint" ng-disabled="policyLock" />
</div>
+ <!--
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-checked="savePrototype" ng-click="savePrototype = !savePrototype">
+ Make this Policy as Prototype
+ </label>
+ </div> -->
</div>
</div>
</div>
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyPrototypes.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyPrototypes.html
new file mode 100644
index 0000000..9f22ce4
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyPrototypes.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">
+ <span class="fa fa-code-fork"></span>
+ <h3 class="box-title">
+ Prototype List
+ </h3>
+ </div>
+ <div class="box-body">
+ <div sort-table="prototypeList">
+ <table class="table table-bordered table-hover">
+ <thead>
+ <tr>
+ <th>
+ <input type="checkbox" ng-checked="getCheckedList().length === prototypeList.length && prototypeList.length !== 0" ng-click="doCheckAll()" />
+ </th>
+ <th>Name</th>
+ <th>Definition</th>
+ <th>Publishers</th>
+ <th width="110">Operation</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input type="checkbox" ng-checked="checkedPrototypes[item.name]" ng-click="checkedPrototypes[item.name] = !checkedPrototypes[item.name]" />
+ </td>
+ <td>{{item.name}}</td>
+ <td><pre>{{item.definition.definition.value}}</pre></td>
+ <td>
+ <ul class="no-margin">
+ <li ng-repeat="publisher in item.alertPublishmentIds track by $index">
+ {{publisher}}
+ </li>
+ </ul>
+ </td>
+ <td class="text-center">
+ <button class="btn btn-xs btn-primary" ng-click="createPolicy([item])">Export</button>
+ <button class="btn btn-xs btn-danger" ng-click="deletePrototype(item)">Delete</button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ </div>
+ <div class="box-footer text-right">
+ <button class="btn btn-primary" ng-click="groupCreate()" ng-disabled="getCheckedList().length === 0">Export as Policies</button>
+ </div>
+</div>
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/app.js b/eagle-server/src/main/webapp/app/dev/public/js/app.js
index 9948ef5..f136241 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/app.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/app.js
@@ -43,7 +43,7 @@
// ======================================================================================
// = Router config =
// ======================================================================================
- var defaultRouterStates = ['site', 'alertList', 'policyList', 'streamList', 'policyCreate', 'policyEdit', 'alertDetail', 'policyDetail'];
+ var defaultRouterStates = ['site', 'alertList', 'policyList', 'streamList', 'policyPrototypes', 'policyCreate', 'policyEdit', 'alertDetail', 'policyDetail'];
function routeResolve(config) {
var resolve = {};
@@ -201,6 +201,12 @@
resolve: routeResolve()
})
+ .state('policyPrototypes', {
+ url: "/site/:siteId/policy/prototypes",
+ templateUrl: "partials/alert/policyPrototypes.html?_=" + window._TRS(),
+ controller: "policyPrototypesCtrl",
+ resolve: routeResolve()
+ })
.state('policyCreate', {
url: "/site/:siteId/policy/create",
templateUrl: "partials/alert/policyEdit/main.html?_=" + window._TRS(),
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js b/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
index 7a312c9..8e74824 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
@@ -193,6 +193,12 @@
).appendTo($toolContainer);
$compile($pageSize)($scope);
+ // Non-Sort Column
+ $element.find("table thead th:not([sortpath])").each(function () {
+ var $this = $(this);
+ $compile($this)($scope);
+ });
+
// Sort Column
$element.find("table [sortpath]").each(function () {
var $this = $(this);
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
index 743b4b6..2bf62f5 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
@@ -186,7 +186,7 @@
};
});
- eagleControllers.controller('policyDetailCtrl', function ($scope, $wrapState, $interval, PageConfig, Time, Entity, CompatibleEntity, Policy) {
+ eagleControllers.controller('policyDetailCtrl', function ($scope, $wrapState, $interval, UI, PageConfig, Time, Entity, CompatibleEntity, Policy) {
PageConfig.title = "Policy";
PageConfig.subTitle = "Detail";
PageConfig.navPath = [
@@ -283,6 +283,33 @@
Policy.stop($scope.policy).then(updatePolicy);
};
+ $scope.makePrototype = function () {
+ $.dialog({
+ title: 'Confirm',
+ content: 'Do you want to make this policy as Prototype?',
+ confirm: true,
+ }, function (ret) {
+ if (!ret) return;
+
+ Entity.post('policyProto/create/' + $scope.policy.name, '')._then(function (res) {
+ var validate = res.data;
+ console.log(validate);
+ if(!validate.success) {
+ $.dialog({
+ title: "OPS",
+ content: validate.message
+ });
+ return;
+ } else {
+ $.dialog({
+ title: "Success",
+ content: "Please go to the prototype page to check updates"
+ });
+ }
+ });
+ });
+ };
+
var refreshInterval = $interval($scope.alertList._refresh, 1000 * 60);
$scope.$on('$destroy', function() {
$interval.cancel(refreshInterval);
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js
index 9308554..e4623b2 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js
@@ -31,6 +31,100 @@
policyEditController.apply(this, newArgs);
}
+ eagleControllers.controller('policyPrototypesCtrl', function ($scope, $wrapState, Site, PageConfig, Entity, UI) {
+ PageConfig.title = "Policy Prototypes";
+
+ $scope.checkedPrototypes = {};
+
+ function refreshPrototypeList() {
+ $scope.prototypeList = Entity.query('policyProto');
+ }
+
+ $scope.deletePrototype = function (prototype) {
+ UI.deleteConfirm(prototype.name)(function (entity, closeFunc) {
+ Entity.delete("policyProto/" + prototype.uuid)._promise.then(function (res) {
+ var data = res.data;
+
+ if (data.success !== true) {
+ $.dialog({
+ title: 'OPS',
+ content: data.message
+ });
+ }
+ }).finally(function () {
+ closeFunc();
+ refreshPrototypeList();
+ });
+ });
+ };
+
+ $scope.getCheckedList = function () {
+ return $.map($scope.prototypeList, function (proto) {
+ return $scope.checkedPrototypes[proto.name] ? proto : null;
+ });
+ };
+
+ $scope.doCheckAll = function () {
+ if ($scope.getCheckedList().length === $scope.prototypeList.length) {
+ $scope.checkedPrototypes = {};
+ } else {
+ $.each($scope.prototypeList, function (i, proto) {
+ $scope.checkedPrototypes[proto.name] = true;
+ });
+ }
+ };
+
+ $scope.groupCreate = function () {
+ var list = $scope.getCheckedList();
+ $scope.createPolicy(list);
+ };
+
+ $scope.createPolicy = function (protoList) {
+ if (protoList.length === 0) {
+ $.dialog({
+ title: 'OPS',
+ content: 'Please select at least one prototype.',
+ });
+ return;
+ }
+
+ UI.createConfirm('Policy', {}, [
+ {field: "siteId", name: "Site Id", type: 'select', valueList: $.map(Site.list, function (site) {
+ return site.siteId;
+ })}
+ ])(function (entity, closeFunc, unlock) {
+ var nameList = $.map(protoList, function (proto) {
+ return proto.name;
+ });
+ Entity.create("policyProto/exportByName/" + entity.siteId, nameList)._then(function (res) {
+ var data = res.data;
+
+ if(!data.success) {
+ $.dialog({
+ title: 'OPS',
+ content: data.message
+ });
+ unlock();
+ return;
+ } else {
+ $.dialog({
+ title: 'Success',
+ content: 'Click confirm to go to the policy page.',
+ confirm: true,
+ }, function (ret) {
+ if (!ret) return;
+
+ $wrapState.go('policyList', {siteId: entity.siteId });
+ });
+ }
+ closeFunc();
+ }, unlock);
+ });
+ };
+
+ refreshPrototypeList();
+ });
+
eagleControllers.controller('policyCreateCtrl', function ($scope, $q, $wrapState, $timeout, PageConfig, Entity, Policy) {
PageConfig.title = "Define Policy";
connectPolicyEditController({}, arguments);
@@ -88,6 +182,7 @@
$scope.streamGroups = {};
$scope.newPolicy = !$scope.policy.name;
$scope.autoPolicyDescription = $scope.newPolicy && !$scope.policy.description;
+ $scope.savePrototype = false;
PageConfig.navPath = [
{title: "Policy List", path: "/policies"},
@@ -445,28 +540,31 @@
$q.all(publisherPromiseList).then(function () {
console.log("Create publishers success...");
- // Create policy
- Entity.create("metadata/policies", $scope.policy)._then(function () {
+ var publisherNameList = $.map($scope.policyPublisherList, function (publisher) {
+ return publisher.name;
+ });
+
+ var policyPromise;
+
+ if ($scope.savePrototype) {
+ policyPromise = Entity.create('policyProto/create?needPolicyProtoCreated=true', {
+ definition: $scope.policy,
+ alertPublishmentIds: publisherNameList
+ });
+ } else {
+ policyPromise = Entity.create('policyProto/create?needPolicyProtoCreated=false', {
+ definition: $scope.policy,
+ alertPublishmentIds: publisherNameList
+ });
+ }
+
+ policyPromise._then(function () {
console.log("Create policy success...");
- // Link with publisher
- Entity.post("metadata/policies/" + $scope.policy.name + "/publishments/", $.map($scope.policyPublisherList, function (publisher) {
- return publisher.name;
- }))._then(function () {
- // Link Success
- $.dialog({
- title: "Done",
- content: "Close dialog to go to the policy detail page."
- }, function () {
- $wrapState.go("policyDetail", {name: $scope.policy.name, siteId: $scope.policy.siteId});
- });
- }, function (res) {
- // Link Failed
- $.dialog({
- title: "OPS",
- content: "Link publishers failed:" + res.data.message
- });
- }).finally(function () {
- $scope.policyLock = false;
+ $.dialog({
+ title: "Done",
+ content: "Close dialog to go to the policy detail page."
+ }, function () {
+ $wrapState.go("policyDetail", {name: $scope.policy.name, siteId: $scope.policy.siteId});
});
}, function (res) {
var errormsg = "";
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/authSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/authSrv.js
index 0fa2d4a..1b11902 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/services/authSrv.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/authSrv.js
@@ -21,8 +21,10 @@
var serviceModule = angular.module('eagle.service');
- serviceModule.service('Auth', function ($http) {
+ serviceModule.service('Auth', function ($http, $q) {
//$http.defaults.withCredentials = true;
+ var _promise;
+
var Auth = {
isLogin: false,
user: {},
@@ -48,7 +50,7 @@
};
Auth.sync = function (hash) {
- return $http.get(_host + "/rest/auth/principal", {
+ _promise = $http.get(_host + "/rest/auth/principal", {
headers: {
'Authorization': "Basic " + hash
}
@@ -64,10 +66,20 @@
}, function () {
return false;
});
+
+ return _promise;
+ };
+
+ Auth.getPromise = function () {
+ return _promise;
};
if (localStorage && localStorage.getItem('auth')) {
Auth.sync(localStorage.getItem('auth'));
+ } else {
+ var deferred = $q.defer();
+ deferred.resolve();
+ _promise = deferred.promise;
}
Object.defineProperties(Auth, {
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
index 2978a35..cdacbb5 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
@@ -34,10 +34,16 @@
list._promise = promise.then(function (res) {
var data = res.data;
list.splice(0);
- Array.prototype.push.apply(list, data.data);
+ if (typeof data.data === 'object') {
+ Array.prototype.push.apply(list, data.data);
+ } else {
+ list.push(data.data);
+ }
list._done = true;
return res;
+ }, function (res) {
+ return res;
});
return withThen(list);
}
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
index 5ce488d..582134c 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
@@ -103,11 +103,14 @@
{name: "Streams", path: "#/site/" + site.siteId + "/streams"},
];
- if (Auth.isAdmin) {
- alertPortal.push(
- {name: "Define Policy", path: "#/site/" + site.siteId + "/policy/create"}
- );
- }
+ Auth.getPromise().then(function () {
+ if (Auth.isAdmin) {
+ alertPortal.push(
+ {name: "Prototypes", path: "#/site/" + site.siteId + "/policy/prototypes"},
+ {name: "Define Policy", path: "#/site/" + site.siteId + "/policy/create"}
+ );
+ }
+ });
return [
{name: site.siteName || site.siteId + " Home", icon: "home", path: "#/site/" + site.siteId},