You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by ji...@apache.org on 2016/11/07 10:05:48 UTC

incubator-eagle git commit: [EAGLE-735] Policy detail UI update

Repository: incubator-eagle
Updated Branches:
  refs/heads/master 300c457c0 -> ceeebb423


[EAGLE-735] Policy detail UI update

Policy detail UI update

Author: zombieJ <sm...@gmail.com>

Closes #617 from zombieJ/EAGLE-735.


Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/ceeebb42
Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/ceeebb42
Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/ceeebb42

Branch: refs/heads/master
Commit: ceeebb42371dc00bc39f65269d00de6145e73aca
Parents: 300c457
Author: zombieJ <sm...@gmail.com>
Authored: Mon Nov 7 18:05:38 2016 +0800
Committer: zombieJ <sm...@gmail.com>
Committed: Mon Nov 7 18:05:38 2016 +0800

----------------------------------------------------------------------
 eagle-server/src/main/webapp/app/dev/index.html |   1 +
 .../app/dev/partials/alert/policyDetail.html    | 147 ++++++++++++-------
 .../partials/alert/policyEdit/advancedMode.html |  46 +++---
 .../app/dev/partials/integration/site.html      |   4 +-
 .../src/main/webapp/app/dev/public/css/main.css |   7 +-
 .../src/main/webapp/app/dev/public/js/app.js    |   3 +-
 .../src/main/webapp/app/dev/public/js/common.js |   6 +
 .../webapp/app/dev/public/js/ctrls/alertCtrl.js |  54 +++----
 .../app/dev/public/js/ctrls/alertEditCtrl.js    |  49 ++-----
 .../app/dev/public/js/services/policySrv.js     |  73 +++++++++
 10 files changed, 245 insertions(+), 145 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/index.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/index.html b/eagle-server/src/main/webapp/app/dev/index.html
index c9b25b2..da00bd0 100644
--- a/eagle-server/src/main/webapp/app/dev/index.html
+++ b/eagle-server/src/main/webapp/app/dev/index.html
@@ -266,6 +266,7 @@
 		<script src="public/js/services/siteSrv.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/services/applicationSrv.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/services/uiSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/policySrv.js" type="text/javascript" charset="utf-8"></script>
 
 		<!-- Components -->
 		<script src="public/js/components/main.js" type="text/javascript" charset="utf-8"></script>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html
----------------------------------------------------------------------
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 d504d1e..fb7be0c 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
@@ -16,37 +16,71 @@
   limitations under the License.
   -->
 
-<div class="nav-tabs-custom">
-	<ul class="nav nav-tabs">
-		<li class="active"><a href="#basic" data-toggle="tab">Basic</a></li>
-		<li><a href="#definition" data-toggle="tab">Definition</a></li>
-		<li><a href="#publisher" data-toggle="tab">Publisher</a></li>
-
-		<li class="box-tools pull-right">
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<span class="fa fa-square" ng-class="policy.policyStatus === 'ENABLED' ? 'text-green' : 'text-muted'"></span>
+		<h3 class="box-title">
+			{{policy.name}}
+		</h3>
+		<div class="box-tools pull-right">
 			<div class="btn-group">
 				<button class="btn btn-default fa fa-play" ng-click="startPolicy()" ng-if="policy.policyStatus !== 'ENABLED'"></button>
 				<button class="btn btn-default fa fa-square" ng-click="stopPolicy()" ng-if="policy.policyStatus === 'ENABLED'"></button>
 				<a class="btn btn-default fa fa-pencil" ui-sref="policyEdit({name: policy.name})"></a>
 				<button class="btn btn-danger fa fa-trash" ng-click="deletePolicy()"></button>
 			</div>
-		</li>
+		</div>
+	</div>
+	<div class="box-body">
+		<table class="table">
+			<tbody>
+				<tr>
+					<th>Status</th>
+					<td>
+						<span class="label" ng-class="policy.policyStatus === 'ENABLED' ? 'label-success' : 'label-default'">
+							{{policy.policyStatus}}
+						</span>
+					</td>
+				</tr>
+				<tr>
+					<th>Definition</th>
+					<td><pre class="text-break">{{policy.definition.value}}</pre></td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>
+
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<!--li><a href="#statistic" data-toggle="tab">Statistic</a></li>
+		<li><a href="#alerts" data-toggle="tab">Alerts</a></li-->
+		<li class="active"><a href="#setting" data-toggle="tab">Setting</a></li>
+		<li><a href="#assignments" data-toggle="tab">Assignments</a></li>
 	</ul>
 	<div class="tab-content">
-		<div class="tab-pane active" id="basic">
+		<div class="tab-pane active" id="setting">
 			<table class="table">
 				<tbody>
 					<tr>
-						<th>Name</th>
-						<td>
-							<span class="fa fa-square" ng-class="policy.policyStatus === 'ENABLED' ? 'text-green' : 'text-muted'"></span>
-							{{policy.name}}
-						</td>
 						<th>Parallelism Hint</th>
 						<td>{{policy.parallelismHint}}</td>
 					</tr>
 					<tr>
-						<th>Description</th>
-						<td colspan="3"><pre class="inline">{{policy.description}}</pre></td>
+						<th>Partitions</th>
+						<td>
+							<ul class="no-margin">
+								<li ng-repeat="partition in policy.partitionSpec track by $index">
+									[<span class="text-primary">{{partition.type}}</span>]
+									{{partition.streamId}}:
+									<strong class="text-success">{{partition.columns.join(", ")}}</strong>
+								</li>
+								<li ng-if="policy.partitionSpec.length === 0" class="text-muted">
+									No partition
+								</li>
+							</ul>
+						</td>
 					</tr>
 					<tr>
 						<th>Input Streams</th>
@@ -57,6 +91,8 @@
 								</li>
 							</ul>
 						</td>
+					</tr>
+					<tr>
 						<th>Output Streams</th>
 						<td>
 							<ul class="no-margin">
@@ -66,50 +102,61 @@
 							</ul>
 						</td>
 					</tr>
+					<tr>
+						<th>Publishers</th>
+						<td>
+							<ul class="no-margin">
+								<li class="text-danger" ng-if="policyPublisherList.length === 0">
+									<i class="fa fa-fw fa-warning"></i> No alert publisher defined
+								</li>
+								<li ng-repeat="publisher in policyPublisherList track by $index">
+									<span>
+										<strong>
+											<span>({{Policy.publisherTypes[publisher.type].name}})</span>
+											{{publisher.name}}
+										</strong>
+									</span>
+									<p class="offset" ng-repeat="field in Policy.publisherTypes[publisher.type].displayFields track by $index">
+										<span>{{field}}:</span>
+										<span>{{publisher.properties[field]}}</span>
+									</p>
+								</li>
+							</ul>
+						</td>
+					</tr>
+					<tr>
+						<th class="text-no-break">Execution Plan</th>
+						<td><pre>{{executionPlan.policyExecutionPlan.executionPlanDesc}}</pre></td>
+					</tr>
 				</tbody>
 			</table>
 		</div>
-		<div class="tab-pane" id="definition">
-			<label>Definition</label>
-			<pre>{{policy.definition.value}}</pre>
-
-			<label>Partition</label>
-			<ul class="no-margin">
-				<li ng-repeat="partition in policy.partitionSpec track by $index">
-					[<span class="text-primary">{{partition.type}}</span>]
-					{{partition.streamId}}:
-					<strong class="text-success">{{partition.columns.join(", ")}}</strong>
-				</li>
-				<li ng-if="policy.partitionSpec.length === 0" class="text-muted">
-					No partition
-				</li>
-			</ul>
+		<div class="tab-pane" id="statistic">statistic
 		</div>
-		<div class="tab-pane" id="publisher">
-			<p ng-if="publisherList.length === 0" class="text-muted">
-				<span class="fa fa-exclamation-triangle"></span> No publisher configured
-			</p>
-			<table class="table table-bordered" ng-repeat="publisher in publisherList track by publisher.name">
+		<div class="tab-pane" id="alerts">alerts
+		</div>
+		<div class="tab-pane" id="assignments">
+			<table class="table">
 				<tbody>
 					<tr>
-						<th width="100" class="text-no-break">Name</th>
-						<td>{{publisher.name}}</td>
-						<th width="150" class="text-no-break">DeDup Interval Min</th>
-						<td>{{publisher.dedupIntervalMin}}</td>
+						<th>Version</th>
+						<td>{{assignment.version}}</td>
 					</tr>
 					<tr>
-						<th>Type</th>
-						<td colspan="3">{{publisher.type}}</td>
+						<th>Queue Id</th>
+						<td>{{assignment.queueId}}</td>
 					</tr>
 					<tr>
-						<th>Properties</th>
-						<td colspan="3">
-							<ul class="no-margin">
-								<li ng-repeat="(key, value) in publisher.properties track by key">
-									<strong>{{key}}:</strong>
-									{{value}}
-								</li>
-							</ul>
+						<th class="text-no-break">working Slots</th>
+						<td>
+							<div na-block="queue.workingSlots.length > 0">
+								<ul class="no-margin">
+									<li ng-repeat="slot in queue.workingSlots track by $index">
+										<span class="text-primary">{{slot.topologyName}}</span>
+										- {{slot.boltId}}
+									</li>
+								</ul>
+							</div>
 						</td>
 					</tr>
 				</tbody>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html
----------------------------------------------------------------------
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 c436ae7..325f5a9 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
@@ -49,24 +49,26 @@
 					</p>
 
 					<p ng-hide="getSearchApplication()" class="text-warning">No stream match</p>
-					<div ng-show="getSearchApplication()" ng-repeat="(app, streams) in getSearchApplication() track by app" class="policy-app-list">
-						<a data-toggle="collapse" href="[data-id='SP_{{app}}']">
-							<strong>{{app}}</strong>
-						</a>
-						<ul data-id="SP_{{app}}" class="collapse list-unstyled">
-							<li ng-repeat="stream in streams track by stream.streamId">
-								<a data-toggle="collapse" href="[data-id='SP_{{app}}_{{stream.streamId}}']">
-									<span class="fa fa-{{isInputStreamSelected(stream.streamId) ? 'check-square' : 'square'}}"></span>
-									{{stream.streamId}}
-								</a>
-								<ul data-id="SP_{{app}}_{{stream.streamId}}" class="collapse in">
-									<li ng-repeat="column in stream.columns track by $index">
-										<span class="text-primary">[{{column.type}}]</span>
-										{{column.name}}
-									</li>
-								</ul>
-							</li>
-						</ul>
+					<div ng-show="getSearchApplication()">
+						<div ng-repeat="(app, streams) in getSearchApplication() track by app" class="policy-app-list">
+							<a data-toggle="collapse" href="[data-id='SP_{{app}}']">
+								<strong class="text-break">{{app}}</strong>
+							</a>
+							<ul data-id="SP_{{app}}" class="collapse list-unstyled">
+								<li ng-repeat="stream in streams track by stream.streamId">
+									<a data-toggle="collapse" href="[data-id='SP_{{app}}_{{stream.streamId}}']"  class="text-break">
+										<span class="fa fa-{{isInputStreamSelected(stream.streamId) ? 'check-square' : 'square'}}"></span>
+										{{stream.streamId}}
+									</a>
+									<ul data-id="SP_{{app}}_{{stream.streamId}}" class="collapse in">
+										<li ng-repeat="column in stream.columns track by $index">
+											<span class="text-primary">[{{column.type}}]</span>
+											{{column.name}}
+										</li>
+									</ul>
+								</li>
+							</ul>
+						</div>
 					</div>
 				</div>
 				<div ng-show="sourceTab === 'selected'">
@@ -159,11 +161,11 @@
 						<span>
 							[<a class="fa fa-times" ng-click="removePublisher(publisher)"></a>]
 							<strong>
-								<span>({{publisherTypes[publisher.type].name}})</span>
+								<span>({{Policy.publisherTypes[publisher.type].name}})</span>
 								{{publisher.name}}
 							</strong>
 						</span>
-						<p class="offset" ng-repeat="field in publisherTypes[publisher.type].displayFields track by $index">
+						<p class="offset" ng-repeat="field in Policy.publisherTypes[publisher.type].displayFields track by $index">
 							<span>{{field}}:</span>
 							<span>{{publisher.properties[field]}}</span>
 						</p>
@@ -287,10 +289,10 @@
 					<div class="form-group">
 						<label>Type</label>
 						<select class="form-control" ng-model="publisher.type">
-							<option ng-repeat="(type, fields) in publisherTypes track by type">{{type}}</option>
+							<option ng-repeat="(type, fields) in Policy.publisherTypes track by type">{{type}}</option>
 						</select>
 					</div>
-					<div class="form-group" ng-repeat="field in publisherTypes[publisher.type].fields track by $index">
+					<div class="form-group" ng-repeat="field in Policy.publisherTypes[publisher.type].fields track by $index">
 						<label>{{field}}</label>
 						<input class="form-control" ng-model="publisher.properties[field]" />
 					</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/site.html b/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
index 07555d1..a13599d 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
@@ -116,7 +116,7 @@
 									<th>Application Name</th>
 									<td>{{application.name}}</td>
 									<th>Type</th>
-									<td>{{application.type}}</td>
+									<td class="text-break">{{application.type}}</td>
 								</tr>
 								<tr ng-if="tmpApp.uuid">
 									<th>Application Id</th>
@@ -169,7 +169,7 @@
 								</tr>
 								<tr ng-if="tmpApp.uuid">
 									<th>Configuration</th>
-									<td colspan="3">
+									<td colspan="3" class="text-break">
 										<ul>
 											<li ng-repeat="(key, value) in tmpApp.configuration track by $index">
 												<strong>{{key}}:</strong>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/css/main.css
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/css/main.css b/eagle-server/src/main/webapp/app/dev/public/css/main.css
index 8da7c5f..e785c6c 100644
--- a/eagle-server/src/main/webapp/app/dev/public/css/main.css
+++ b/eagle-server/src/main/webapp/app/dev/public/css/main.css
@@ -129,7 +129,8 @@ table ul {
 	padding: 0 0 0 20px;
 }
 
-table.table pre {
+table.table pre,
+pre.text-break {
 	white-space: pre-wrap;
 	margin: 0;
 }
@@ -266,6 +267,10 @@ ul.stepGuide li > .title {
 	color: #666;
 }
 
+.box > .box-body.responsive {
+	overflow-x: auto;
+}
+
 /* ========================================================================
  * =                                 Tab                                  =
  * ======================================================================== */

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/js/app.js
----------------------------------------------------------------------
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 55a6eb6..577ba4d 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
@@ -256,7 +256,7 @@ var app = {};
 		// ======================================================================================
 		// =                                   Main Controller                                  =
 		// ======================================================================================
-		eagleApp.controller('MainCtrl', function ($scope, $wrapState, $urlRouter, PageConfig, Portal, Widget, Entity, Site, Application, UI, Time) {
+		eagleApp.controller('MainCtrl', function ($scope, $wrapState, $urlRouter, PageConfig, Portal, Widget, Entity, Site, Application, UI, Time, Policy) {
 			window._WrapState = $scope.$wrapState = $wrapState;
 			window._PageConfig = $scope.PageConfig = PageConfig;
 			window._Portal = $scope.Portal = Portal;
@@ -266,6 +266,7 @@ var app = {};
 			window._Application = $scope.Application = Application;
 			window._UI = $scope.UI = UI;
 			window._Time = $scope.Time = Time;
+			window._Policy = $scope.Policy = Policy;
 			$scope.common = common;
 
 			$scope._TRS = window._TRS();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/js/common.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/common.js b/eagle-server/src/main/webapp/app/dev/public/js/common.js
index e4f806d..d1ea3b3 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/common.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/common.js
@@ -341,6 +341,12 @@
 		return typeof num === "number" && !isNaN(num);
 	};
 
+	common.number.parse = function (num) {
+		num = Number(num);
+		if(isNaN(num)) num = 0;
+		return num;
+	};
+
 	common.number.toFixed = function (num, fixed) {
 		if(!common.number.isNumber(num)) return "-";
 		num = Number(num);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
----------------------------------------------------------------------
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 c33c9ff..9b6defb 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
@@ -21,35 +21,6 @@
 
 	var eagleControllers = angular.module('eagleControllers');
 
-	var serviceModule = angular.module('eagle.service');
-
-	serviceModule.service('Policy', function($q, UI, Entity) {
-		return {
-			delete: function (policy) {
-				var deferred = $q.defer();
-
-				UI.deleteConfirm(policy.name)(function (entity, closeFunc) {
-					Entity.deleteMetadata("policies/" + policy.name)._promise.finally(function () {
-						closeFunc();
-						deferred.resolve();
-					});
-				}, function () {
-					deferred.reject();
-				});
-
-				return deferred.promise;
-			},
-
-			start: function (policy) {
-				return Entity.post("metadata/policies/" + encodeURIComponent(policy.name) + "/status/ENABLED", {})._promise;
-			},
-
-			stop: function (policy) {
-				return Entity.post("metadata/policies/" + encodeURIComponent(policy.name) + "/status/DISABLED", {})._promise;
-			}
-		};
-	});
-
 	// ======================================================================================
 	// =                                        Alert                                       =
 	// ======================================================================================
@@ -130,7 +101,7 @@
 	});
 
 	eagleControllers.controller('policyDetailCtrl', function ($scope, $wrapState, PageConfig, Entity, Policy) {
-		PageConfig.title = $wrapState.param.name;
+		PageConfig.title = "Policy";
 		PageConfig.subTitle = "Detail";
 		PageConfig.navPath = [
 			{title: "Policy List", path: "/policies"},
@@ -138,7 +109,9 @@
 		];
 
 		function updatePolicy() {
-			var policyList = Entity.queryMetadata("policies/" + encodeURIComponent($wrapState.param.name));
+			var policyName = $wrapState.param.name;
+			var encodePolicyName = encodeURIComponent(policyName);
+			var policyList = Entity.queryMetadata("policies/" + encodePolicyName);
 			policyList._promise.then(function () {
 				$scope.policy = policyList[0];
 				console.log("[Policy]", $scope.policy);
@@ -150,9 +123,24 @@
 					}, function () {
 						$wrapState.go("policyList");
 					});
-				} else {
-					$scope.publisherList = Entity.queryMetadata("policies/" + encodeURIComponent($scope.policy.name) + "/publishments");
+					return;
 				}
+
+				Entity.post("metadata/policies/parse", $scope.policy.definition.value)._then(function (res) {
+					$scope.executionPlan = res.data;
+				});
+			});
+
+			$scope.policyPublisherList = Entity.queryMetadata("policies/" + encodePolicyName + "/publishments/");
+
+			Entity.queryMetadata("schedulestates")._then(function (res) {
+				var schedule = res.data || {};
+				$scope.assignment = common.array.find(policyName, schedule.assignments, ["policyName"]) || {};
+
+				var queueList = $.map(schedule.monitoredStreams, function (stream) {
+					return stream.queues;
+				});
+				$scope.queue = common.array.find($scope.assignment.queueId, queueList, ["queueId"]);
 			});
 		}
 		updatePolicy();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js
----------------------------------------------------------------------
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 ed61919..e202450 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
@@ -21,29 +21,6 @@
 
 	var eagleControllers = angular.module('eagleControllers');
 
-	var publisherTypes = {
-		'org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher': {
-			name: "Email",
-			displayFields: ["recipients"],
-			fields: ["subject", "template", "sender", "recipients", "mail.smtp.host", "connection", "mail.smtp.port"]
-		},
-		'org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher': {
-			name: "Kafka",
-			displayFields: ["topic"],
-			fields: ["topic", "kafka_broker", "rawAlertNamespaceLabel", "rawAlertNamespaceValue"]
-		},
-		'org.apache.eagle.alert.engine.publisher.impl.AlertSlackPublisher': {
-			name: "Slack",
-			displayFields: ["channels"],
-			fields: ["token", "channels", "severitys", "urltemplate"]
-		},
-		'org.apache.eagle.alert.engine.publisher.impl.AlertEagleStorePlugin': {
-			name: "Storage",
-			displayFields: [],
-			fields: []
-		},
-	};
-
 	// ======================================================================================
 	// =                                    Policy Create                                   =
 	// ======================================================================================
@@ -54,11 +31,11 @@
 		policyEditController.apply(this, newArgs);
 	}
 
-	eagleControllers.controller('policyCreateCtrl', function ($scope, $q, $wrapState, $timeout, $element, PageConfig, Entity) {
+	eagleControllers.controller('policyCreateCtrl', function ($scope, $q, $wrapState, $timeout, PageConfig, Entity, Policy) {
 		PageConfig.title = "Define Policy";
 		connectPolicyEditController({}, arguments);
 	});
-	eagleControllers.controller('policyEditCtrl', function ($scope, $q, $wrapState, $timeout, $element, PageConfig, Entity) {
+	eagleControllers.controller('policyEditCtrl', function ($scope, $q, $wrapState, $timeout, PageConfig, Entity, Policy) {
 		PageConfig.title = "Edit Policy";
 		var args = arguments;
 
@@ -79,8 +56,8 @@
 		});
 	});
 
-	function policyEditController(policy, $scope, $q, $wrapState, $timeout, $element, PageConfig, Entity) {
-		$scope.publisherTypes = publisherTypes;
+	function policyEditController(policy, $scope, $q, $wrapState, $timeout, PageConfig, Entity, Policy) {
+		$scope.publisherTypes = Policy.publisherTypes;
 
 		$scope.policy = policy;
 		$scope.policy = common.merge({
@@ -324,7 +301,7 @@
 				}, $scope.publisher.existPublisher);
 			}
 			var properties = {};
-			$.each($scope.publisherTypes[$scope.publisher.type].fields, function (i, field) {
+			$.each(Policy.publisherTypes[$scope.publisher.type].fields, function (i, field) {
 				properties[field] = $scope.publisher.properties[field] || "";
 			});
 			$scope.policyPublisherList.push($.extend({}, $scope.publisher, {properties: properties}));
@@ -335,14 +312,14 @@
 		// ==============================================================
 		$scope.saveLock = false;
 		$scope.saveCheck = function () {
-			if($scope.saveLock) return false;
-
-			if(!$scope.policy.name) return false;
-			if(!$scope.policy.parallelismHint) return false;
-			if(!$scope.policy.definition.value) return false;
-			if(!$scope.policy.outputStreams.length) return false;
-			if(!$scope.policyPublisherList.length) return false;
-			return true;
+			return (
+				!$scope.saveLock &&
+				$scope.policy.name &&
+				common.number.parse($scope.policy.parallelismHint) > 0 &&
+				$scope.policy.definition.value &&
+				$scope.policy.outputStreams.length &&
+				$scope.policyPublisherList.length
+			);
 		};
 
 		$scope.saveConfirm = function () {

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ceeebb42/eagle-server/src/main/webapp/app/dev/public/js/services/policySrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/policySrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/policySrv.js
new file mode 100644
index 0000000..c2f3d8c
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/policySrv.js
@@ -0,0 +1,73 @@
+/*
+ * 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 serviceModule = angular.module('eagle.service');
+
+	serviceModule.service('Policy', function($q, UI, Entity) {
+		return {
+			publisherTypes: {
+				'org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher': {
+					name: "Email",
+					displayFields: ["recipients"],
+					fields: ["subject", "template", "sender", "recipients", "mail.smtp.host", "connection", "mail.smtp.port"]
+				},
+				'org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher': {
+					name: "Kafka",
+					displayFields: ["topic"],
+					fields: ["topic", "kafka_broker", "rawAlertNamespaceLabel", "rawAlertNamespaceValue"]
+				},
+				'org.apache.eagle.alert.engine.publisher.impl.AlertSlackPublisher': {
+					name: "Slack",
+					displayFields: ["channels"],
+					fields: ["token", "channels", "severitys", "urltemplate"]
+				},
+				'org.apache.eagle.alert.engine.publisher.impl.AlertEagleStorePlugin': {
+					name: "Storage",
+					displayFields: [],
+					fields: []
+				}
+			},
+
+			delete: function (policy) {
+				var deferred = $q.defer();
+
+				UI.deleteConfirm(policy.name)(function (entity, closeFunc) {
+					Entity.deleteMetadata("policies/" + policy.name)._promise.finally(function () {
+						closeFunc();
+						deferred.resolve();
+					});
+				}, function () {
+					deferred.reject();
+				});
+
+				return deferred.promise;
+			},
+
+			start: function (policy) {
+				return Entity.post("metadata/policies/" + encodeURIComponent(policy.name) + "/status/ENABLED", {})._promise;
+			},
+
+			stop: function (policy) {
+				return Entity.post("metadata/policies/" + encodeURIComponent(policy.name) + "/status/DISABLED", {})._promise;
+			}
+		};
+	});
+})();