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/10/26 02:46:28 UTC

incubator-eagle git commit: Eagle-631 Policy UI refactory

Repository: incubator-eagle
Updated Branches:
  refs/heads/master 852bac94e -> 1f5126a14


Eagle-631 Policy UI refactory

for https://issues.apache.org/jira/browse/EAGLE-627 , use the validate api to check policy.

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

Closes #562 from zombieJ/EAGLE-631.


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

Branch: refs/heads/master
Commit: 1f5126a1481ac3557247e6711e0f94880bd8b662
Parents: 852bac9
Author: zombieJ <sm...@gmail.com>
Authored: Wed Oct 26 10:46:18 2016 +0800
Committer: zombieJ <sm...@gmail.com>
Committed: Wed Oct 26 10:46:18 2016 +0800

----------------------------------------------------------------------
 eagle-server/src/main/webapp/app/dev/index.html |   1 +
 .../webapp/app/dev/partials/alert/main.html     |   7 +-
 .../app/dev/partials/alert/policyEdit.back.html | 108 -----
 .../partials/alert/policyEdit/advancedMode.html | 294 +++++++++++++
 .../app/dev/partials/alert/policyEdit/main.html |  19 +
 .../app/dev/partials/alert/policyList.html      |   8 +-
 .../src/main/webapp/app/dev/public/css/main.css |  58 +++
 .../src/main/webapp/app/dev/public/js/app.js    |  18 +-
 .../dev/public/js/components/staticInclude.js   |  33 ++
 .../webapp/app/dev/public/js/ctrls/alertCtrl.js |  19 -
 .../dev/public/js/ctrls/alertEditCtrl.bac.js    | 297 +++++++++++++
 .../app/dev/public/js/ctrls/alertEditCtrl.js    | 436 ++++++++++++-------
 .../app/dev/public/js/services/pageSrv.js       |   2 +-
 13 files changed, 985 insertions(+), 315 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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 d791819..64c8c57 100644
--- a/eagle-server/src/main/webapp/app/dev/index.html
+++ b/eagle-server/src/main/webapp/app/dev/index.html
@@ -242,6 +242,7 @@
 		<script src="public/js/components/sortTable.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/components/chart.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/components/widget.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/staticInclude.js" type="text/javascript" charset="utf-8"></script>
 
 		<!-- Controllers -->
 		<script src="public/js/ctrls/main.js" type="text/javascript" charset="utf-8"></script>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/main.html b/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
index 2e14db9..83db262 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
@@ -18,14 +18,9 @@
 
 <div class="nav-tabs-custom">
 	<ul class="nav nav-tabs">
-		<li ng-class="{active: getState() === 'alert.list'}"><a href="#/alert/">Alerts</a></li>
+		<!--li ng-class="{active: getState() === 'alert.list'}"><a href="#/alert/">Alerts</a></li-->
 		<li ng-class="{active: getState() === 'alert.policyList'}"><a href="#/alert/policyList">Policies</a></li>
 		<li ng-class="{active: getState() === 'alert.streamList'}"><a href="#/alert/streamList">Streams</a></li>
-
-		<li
-			ng-class="{active: ['alert.policyCreate', 'alert.policyEdit'].indexOf(getState()) >= 0}"
-			ng-if="['alert.policyCreate', 'alert.policyEdit'].indexOf(getState()) >= 0"
-		><a href="#/alert/policyCreate">Define Alert Policy</a></li>
 	</ul>
 	<div class="tab-content no-padding">
 		<div ui-view></div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
deleted file mode 100644
index 3c335f7..0000000
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
+++ /dev/null
@@ -1,108 +0,0 @@
-<!--
-  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-body">
-	<ul class="timeline">
-		<!-- Base Info -->
-		<li class="time-label">
-			<span class="bg-blue">#1. Basic Information</span>
-		</li>
-		<li>
-			<span class="fa fa-file-text bg-aqua"></span>
-			<div class="timeline-item">
-				<div class="timeline-body">
-					<div class="form-group">
-						<label>Policy Name</label>
-						<input type="text" class="form-control" ng-model="policy.name" />
-					</div>
-					<div class="form-group">
-						<label>Severity</label>
-						<select class="form-control" ng-model="policy.severity">
-							<option>WARNING</option>
-							<option>CRITICAL</option>
-							<option>DANGER</option>
-						</select>
-					</div>
-					<div class="form-group">
-						<label>Description</label>
-						<textarea class="form-control" ng-model="policy.description" rows="3"></textarea>
-					</div>
-				</div>
-			</div>
-		</li>
-
-		<!-- Alert Stream -->
-		<li class="time-label">
-			<span class="bg-blue">#2. Alert Stream</span>
-		</li>
-		<li>
-			<span class="fa fa-rocket bg-aqua"></span>
-			<div class="timeline-item">
-				<div class="timeline-body">
-					<div class="form-group">
-						<label>App Integration</label>
-						<select class="form-control"></select>
-					</div>
-					<div class="form-group">
-						<label>Alert Stream</label>
-						<select class="form-control"></select>
-					</div>
-				</div>
-			</div>
-		</li>
-
-		<!-- Streaming Logic -->
-		<li class="time-label">
-			<span class="bg-blue">#3. Streaming Logic</span>
-		</li>
-		<li>
-			<span class="fa fa-trophy bg-aqua"></span>
-			<div class="timeline-item">
-				<div class="timeline-body">
-					<div class="form-group">
-						<label>Policy Type</label>
-						<select class="form-control"></select>
-					</div>
-					<div class="form-group">
-						<label>Policy Logic</label>
-						<textarea class="form-control" rows="5"></textarea>
-					</div>
-				</div>
-			</div>
-		</li>
-
-		<!-- Publication Configuration -->
-		<li class="time-label">
-			<span class="bg-blue">#4. Publication Configuration</span>
-		</li>
-		<li>
-			<span class="fa fa-envelope bg-aqua"></span>
-			<div class="timeline-item">
-				<div class="timeline-body">
-					<div class="form-group">
-						<label>Publication Type</label>
-						<select class="form-control">
-							<option>NOTIFICATION</option>
-						</select>
-					</div>
-					<a>+ New Publication</a>
-				</div>
-			</div>
-		</li>
-	</ul>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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
new file mode 100644
index 0000000..624acaa
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/advancedMode.html
@@ -0,0 +1,294 @@
+<!--
+  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="row flex">
+	<div class="col-md-4">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<span class="fa fa-rocket"></span>
+				<h3 class="box-title">
+					Source Stream
+				</h3>
+				<div class="box-tools pull-right">
+					<div class="btn-group" data-toggle="btn-toggle">
+						<button class="btn btn-default btn-sm" ng-class="{active: sourceTab === 'all'}" ng-click="setSourceTab('all')">
+							All
+						</button>
+						<button class="btn btn-default btn-sm" ng-class="{active: sourceTab === 'selected'}" ng-click="setSourceTab('selected')">
+							Selected
+						</button>
+					</div>
+				</div>
+			</div>
+			<div class="box-body">
+				<div ng-show="sourceTab === 'all'">
+					<div class="input-with-icon">
+						<input type="text" class="form-control" placeholder="Search..." ng-model="searchSourceKey" />
+						<span class="fa fa-search"></span>
+					</div>
+					<div 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 in 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>
+				</div>
+				<div ng-show="sourceTab === 'selected'">
+					<div ng-repeat="stream in policy.inputStreams">
+						<a data-toggle="collapse" href="[data-id='SS_{{stream}}']">
+							<strong>{{stream}}</strong>
+						</a>
+						<ul class="sm-padding collapse in" data-id="SS_{{stream}}">
+							<li ng-repeat="column in streams[stream].columns track by $index">
+								<span class="text-primary">[{{column.type}}]</span>
+								{{column.name}}
+							</li>
+						</ul>
+					</div>
+					<p class="text-muted" ng-if="policy.inputStreams.length === 0">No Stream selected</p>
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="col-md-8">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<span class="fa fa-pencil"></span>
+				<h3 class="box-title">Policy</h3>
+			</div>
+			<div class="box-body">
+				<div class="row">
+					<div class="col-md-8">
+						<div class="form-group">
+							<label>* Name</label>
+							<input type="text" class="form-control" ng-model="policy.name" ng-readonly="newPolicy" ng-disabled="policyLock" />
+						</div>
+					</div>
+					<div class="col-md-4">
+						<div class="form-group">
+							<label>* Parallelism Hint</label>
+							<input type="text" class="form-control" ng-model="policy.parallelismHint" ng-disabled="policyLock" />
+						</div>
+					</div>
+				</div>
+
+
+				<div class="form-group">
+					<label>Description</label>
+					<textarea class="form-control" ng-model="policy.description" rows="2" ng-disabled="policyLock"></textarea>
+				</div>
+
+				<div class="form-group" ng-class="{'has-error': !!definitionMessage}">
+					<label>* Definition</label>
+					<textarea class="form-control" ng-model="policy.definition.value" rows="10" ng-change="checkDefinition()" ng-disabled="policyLock"></textarea>
+					<p class="text-danger">{{definitionMessage}}</p>
+				</div>
+
+				<!--label>
+					Input Stream Partition
+				</label>
+				<ul class="sm-padding">
+					<li ng-repeat="partition in policy.partitionSpec track by $index">
+						<[<a class="fa fa-times" ng-click="removePartition(partition)"></a>]>
+						<strong class="text-primary">{{partition.streamId}}:</strong>
+						<p>
+							<strong>{{partition.type}}</strong> on
+							<strong class="text-success">[{{partition.columns.join(", ")}}]</strong>
+							<span ng-if="partition.sortSpec.windowPeriod">
+								sort in window:
+								<strong class="text-info">[{{partition.sortSpec.windowPeriod}}]</strong>
+							</span>
+						</p>
+					</li>
+					<li class="text-warning" ng-if="policy.partitionSpec.length === 0">No partition yet.</li>
+					<li>
+						<a ng-click="addPartition()">+ Add Partition</a>
+					</li>
+				</ul-->
+
+				<label>
+					Alert Stream
+				</label>
+				<ul class="sm-padding">
+					<li ng-repeat="stream in outputStreams track by $index">
+						<label>
+							<input type="checkbox" ng-checked="isOutputStreamSelected(stream)" ng-click="checkOutputStream(stream)" ng-disabled="policyLock" />
+							{{stream}}
+						</label>
+					</li>
+					<li class="text-warning" ng-if="policy.outputStreams.length === 0">No alert stream yet.</li>
+				</ul>
+
+				<label>
+					Publisher
+				</label>
+				<ul class="sm-padding">
+					<li ng-repeat="publisher in policyPublisherList track by $index">
+						<span>
+							[<a class="fa fa-times" ng-click="removePublisher(publisher)"></a>]
+							<strong>
+								<span>({{publisherTypes[publisher.type].name}})</span>
+								{{publisher.name}}
+							</strong>
+						</span>
+						<p class="offset" ng-repeat="field in publisherTypes[publisher.type].displayFields track by $index">
+							<span>{{field}}:</span>
+							<span>{{publisher.properties[field]}}</span>
+						</p>
+					</li>
+					<li>
+						<a ng-click="addPublisher()">+ Add Publisher</a>
+					</li>
+				</ul>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="text-right">
+	<button class="btn btn-primary" ng-disabled="!saveCheck()" ng-click="saveConfirm()">
+		<span class="fa fa-floppy-o"></span>
+		Save
+	</button>
+</div>
+
+<!------------------------- Modal: Partition ------------------------->
+<div class="modal fade" tabindex="-1" data-id="partitionMDL">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Add Partition</h4>
+			</div>
+			<div class="modal-body">
+				<div class="form-group">
+					<label>Stream</label>
+					<select class="form-control" ng-model="partition.streamId">
+						<option ng-repeat="stream in policy.inputStreams track by stream">{{stream}}</option>
+					</select>
+				</div>
+				<div class="form-group">
+					<label>Type</label>
+					<select class="form-control" ng-model="partition.type">
+						<option value="GROUPBY">GroupBy</option>
+						<option value="GLOBAL">Global</option>
+						<option value="SHUFFLE">Shuffle</option>
+					</select>
+				</div>
+				<div class="form-group">
+					<label>columns</label>
+					<ul class="list-unstyled">
+						<li ng-repeat="column in streams[partition.streamId].columns track by $index">
+							<label>
+								<input type="checkbox" ng-checked="newPartitionCheckColumn(column.name)"
+									   ng-click="newPartitionClickColumn(column.name)" />
+								<span class="text-primary">[{{column.type}}]</span>
+								{{column.name}}
+							</label>
+						</li>
+					</ul>
+				</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" data-dismiss="modal"
+						ng-disabled="partition.columns.length === 0" ng-click="addPartitionConfirm()">Add</button>
+			</div>
+		</div>
+	</div>
+</div>
+
+<!------------------------- Modal: Publisher ------------------------->
+<div class="modal fade" tabindex="-1" data-id="publisherMDL">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Add Publisher</h4>
+			</div>
+			<div class="modal-body">
+				<div class="radio" ng-show="publisherList.length > 0">
+					<label class="radio-inline">
+						<input type="radio" value="exist" ng-model="addPublisherType">
+						From exist publisher
+					</label>
+					<label class="radio-inline">
+						<input type="radio" value="new" ng-model="addPublisherType">
+						Create new publisher
+					</label>
+				</div>
+
+				<!-- Publisher: Exist -->
+				<div ng-if="addPublisherType === 'exist'">
+					<div class="form-group">
+						<label>Publisher</label>
+						<select class="form-control" ng-model="publisher.existPublisher"
+								ng-options="publisher as publisher.name for publisher in publisherList track by publisher.name"></select>
+					</div>
+				</div>
+
+				<div ng-if="addPublisherType === 'new'">
+					<div class="form-group" ng-class="{'has-error': checkPublisherName()}">
+						<label>
+							Name
+							<small ng-if="checkPublisherName()">({{checkPublisherName()}})</small>
+						</label>
+						<input class="form-control" ng-model="publisher.name" />
+					</div>
+					<div class="form-group">
+						<label>Dedup-Interval Minutes</label>
+						<input class="form-control" ng-model="publisher.dedupIntervalMin" />
+					</div>
+					<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>
+						</select>
+					</div>
+					<div class="form-group" ng-repeat="field in publisherTypes[publisher.type].fields track by $index">
+						<label>{{field}}</label>
+						<input class="form-control" ng-model="publisher.properties[field]" />
+					</div>
+				</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" data-dismiss="modal"
+						ng-disabled="addPublisherType === 'new' && !publisher.name" ng-click="addPublisherConfirm()">Add</button>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/main.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/main.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/main.html
new file mode 100644
index 0000000..db687c4
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit/main.html
@@ -0,0 +1,19 @@
+<!--
+  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 static-include="partials/alert/policyEdit/advancedMode.html?_={{_TRS}}"></div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
index 35d9696..862f944 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
@@ -35,7 +35,7 @@
 							<span class="fa fa-square" ng-class="item.policyStatus === 'ENABLED' ? 'text-green' : 'text-muted'"></span>
 						</td>
 						<td>
-							<a ui-sref="policyDetail({name: item.name})" target="_blank">{{item.name}}</a>
+							<a ui-sref="policyDetail({name: item.name})">{{item.name}}</a>
 						</td>
 						<td class="text-center"><span class="label label-primary">{{item.definition.type}}</span></td>
 						<td>{{item.description}}</td>
@@ -47,7 +47,7 @@
 								<button class="btn btn-default opt" ng-if="item.policyStatus === 'ENABLED'" ng-click="stopPolicy(item)">
 									<span class="fa fa-stop"></span>
 								</button>
-								<a ui-sref="alert.policyEdit({name: item.name})" target="_blank" class="btn btn-default opt"><span class="fa fa-pencil"></span></a>
+								<a ui-sref="policyEdit({name: item.name})" class="btn btn-default opt"><span class="fa fa-pencil"></span></a>
 								<button class="btn btn-danger opt" ng-click="deletePolicy(item)"><span class="fa fa-trash"></span></button>
 							</div>
 						</td>
@@ -58,7 +58,7 @@
 
 		<div class="callout callout-warning no-margin" ng-show="policyList._done && policyList.length === 0">
 			<h4>No Policy yet</h4>
-			<p>You have not create policy yet. Click <a href="#/alert/policyCreate">here</a> to create a new policy.</p>
+			<p>You have not create policy yet. Click <a ui-sref="policyCreate()">here</a> to create a new policy.</p>
 		</div>
 	</div>
 
@@ -67,6 +67,6 @@
 	</div>
 
 	<div class="box-footer text-right">
-		<a href="#/alert/policyCreate" class="btn btn-primary">New Policy</a>
+		<a ui-sref="policyCreate()" class="btn btn-primary">New Policy</a>
 	</div>
 </div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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 16136c8..4b7a799 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
@@ -184,6 +184,44 @@ ul.stepGuide li > .title {
 }
 
 /* ========================================================================
+ * =                                Input                                 =
+ * ======================================================================== */
+.input-with-icon {
+	position: relative;
+	margin-bottom: 10px;
+}
+
+.input-with-icon .fa {
+	position: absolute;
+	left: 10px;
+	top: 10px;
+	pointer-events: none;
+	opacity: 0.6;
+}
+
+.input-with-icon input {
+	padding-left: 28px;
+}
+
+/* ========================================================================
+ * =                                Policy                                =
+ * ======================================================================== */
+.policy-app-list > a {
+	display: block;
+	font-size: 18px;
+}
+
+.policy-app-list > ul {
+	margin: 0;
+	padding: 0 0 0 5px;
+}
+
+.policy-app-list > ul > li input[type='checkbox'] {
+	vertical-align: middle;
+	margin: 0;
+}
+
+/* ========================================================================
  * =                                 Box                                  =
  * ======================================================================== */
 .box .box-title .label {
@@ -270,6 +308,26 @@ ul.stepGuide li > .title {
 	display: inline-block;
 }
 
+ul.sm-padding {
+	padding: 0 0 0 25px;
+}
+
+ul.sm-padding > li {
+	list-style: circle;
+}
+
+ul > li > label {
+	font-weight: normal;
+}
+ul > li > label > input[type='checkbox'] {
+	margin: 0;
+	vertical-align: middle;
+}
+
+ul > li > p.offset {
+	padding-left: 25px;
+}
+
 /* ========================================================================
  * =                                 label                                =
  * ======================================================================== */

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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 39c84c5..277d0fa 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
@@ -109,15 +109,15 @@ var app = {};
 					controller: "alertStreamListCtrl",
 					resolve: routeResolve()
 				})
-				.state('alert.policyCreate', {
-					url: "policyCreate",
-					templateUrl: "partials/alert/policyEdit.html?_=" + window._TRS(),
+				.state('policyCreate', {
+					url: "/alert/policyCreate",
+					templateUrl: "partials/alert/policyEdit/main.html?_=" + window._TRS(),
 					controller: "policyCreateCtrl",
 					resolve: routeResolve()
 				})
-				.state('alert.policyEdit', {
-					url: "policyEdit/{name}",
-					templateUrl: "partials/alert/policyEdit.html?_=" + window._TRS(),
+				.state('policyEdit', {
+					url: "/alert/policyEdit/{name}",
+					templateUrl: "partials/alert/policyEdit/main.html?_=" + window._TRS(),
 					controller: "policyEditCtrl",
 					resolve: routeResolve()
 				})
@@ -185,7 +185,7 @@ var app = {};
 				$stateProvider.state(route.state, config);
 			});
 
-			$httpProvider.interceptors.push(function($q) {
+			/* $httpProvider.interceptors.push(function($q) {
 				function eagleRequestHandle(res) {
 					var data = res.data || {
 						exception: "",
@@ -218,7 +218,7 @@ var app = {};
 						return $q.reject(eagleRequestHandle(res));
 					}
 				};
-			});
+			}); */
 		});
 
 		// ======================================================================================
@@ -236,6 +236,8 @@ var app = {};
 			window._Time = $scope.Time = Time;
 			$scope.common = common;
 
+			$scope._TRS = window._TRS();
+
 			Object.defineProperty(window, "scope", {
 				get: function () {
 					var ele = $("#content .nav-tabs-custom.ng-scope .ng-scope[ui-view], #content .ng-scope[ui-view]").last();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/public/js/components/staticInclude.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/staticInclude.js b/eagle-server/src/main/webapp/app/dev/public/js/components/staticInclude.js
new file mode 100644
index 0000000..707a747
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/staticInclude.js
@@ -0,0 +1,33 @@
+/*
+ * 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 eagleComponents = angular.module('eagle.components');
+
+	eagleComponents.directive('staticInclude', function($http, $templateCache, $compile) {
+		return function(scope, element, attrs) {
+			var templatePath = attrs.staticInclude;
+			$http.get(templatePath, { cache: $templateCache }).success(function(response) {
+				var contents = element.html(response).contents();
+				$compile(contents)(scope);
+			});
+		};
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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 eeea083..e52fc3c 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,25 +21,6 @@
 
 	var eagleControllers = angular.module('eagleControllers');
 
-	// TODO: Mock data
-	var publishmentTypes = [
-		{
-			"type": "email",
-			"className": "org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher",
-			"description": "send alert to email",
-			"enabled":true,
-			"fields": [{"name":"sender"},{"name":"recipients"},{"name":"subject"},{"name":"smtp.server", "value":"host1"},{"name":"connection", "value":"plaintext"},{"name":"smtp.port", "value": "25"}]
-		},
-		{
-			"type": "kafka",
-			"className": "org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher",
-			"description": "send alert to kafka bus",
-			"enabled":true,
-			"fields": [{"name":"kafka_broker","value":"sandbox.hortonworks.com:6667"},{"name":"topic"}]
-		}
-	];
-
-
 	// ======================================================================================
 	// =                                        Main                                        =
 	// ======================================================================================

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.bac.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.bac.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.bac.js
new file mode 100644
index 0000000..3bcd8d8
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.bac.js
@@ -0,0 +1,297 @@
+/*
+ * 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 eagleControllers = angular.module('eagleControllers');
+
+	var publisherTypes = {
+		'org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher': ["subject", "template", "sender", "recipients", "mail.smtp.host", "connection", "mail.smtp.port"],
+		'org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher': ["topic", "kafka_broker", "rawAlertNamespaceLabel", "rawAlertNamespaceValue"],
+		'org.apache.eagle.alert.engine.publisher.impl.AlertSlackPublisher': ["token", "channels", "severitys", "urltemplate"]
+	};
+
+	// ======================================================================================
+	// =                                    Policy Create                                   =
+	// ======================================================================================
+	function connectPolicyEditController(entity, args) {
+		var newArgs = [entity];
+		Array.prototype.push.apply(newArgs, args);
+		/* jshint validthis: true */
+		policyEditController.apply(this, newArgs);
+	}
+
+	eagleControllers.controller('policyCreateCtrl', function ($scope, $wrapState, PageConfig, Entity) {
+		PageConfig.subTitle = "Define Alert Policy";
+		connectPolicyEditController({}, arguments);
+	});
+	eagleControllers.controller('policyEditCtrl', function ($scope, $wrapState, PageConfig, Entity) {
+		PageConfig.subTitle = "Edit Alert Policy";
+		var args = arguments;
+
+		// TODO: Wait for backend data update
+		$scope.policyList = Entity.queryMetadata("policies/" + encodeURIComponent($wrapState.param.name));
+
+		$scope.policyList._promise.then(function () {
+			var policy = $scope.policyList[0];
+
+			if(policy) {
+				connectPolicyEditController(policy, args);
+			} else {
+				$.dialog({
+					title: "OPS",
+					content: "Policy '" + $wrapState.param.name + "' not found!"
+				}, function () {
+					$wrapState.go("alert.policyList");
+				});
+			}
+		});
+	});
+
+	function policyEditController(policy, $scope, $wrapState, PageConfig, Entity) {
+		$scope.newPolicy = !policy.name;
+		$scope.policyLock = false;
+
+		$scope.publisherTypes = publisherTypes;
+
+		$scope.selectedApplication = null;
+		$scope.selectedStream = null;
+
+		$scope.outputStream = "";
+
+		$scope.partitionStream = null;
+		$scope.partitionType = "GROUPBY";
+		$scope.partitionColumns = {};
+
+		$scope.publisherType = "org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher";
+		$scope.publisher = {
+			dedupIntervalMin: "PT1M"
+		};
+		$scope.publisherProps = {};
+
+		$scope.policy = common.merge({
+			name: "",
+			description: "",
+			inputStreams: [],
+			outputStreams: [],
+			definition: {
+				type: "siddhi",
+				value: ""
+			},
+			partitionSpec: [],
+			parallelismHint: 2
+		}, policy);
+
+		$scope.policy.definition = {
+			type: $scope.policy.definition.type,
+			value: $scope.policy.definition.value
+		};
+
+		console.log("[Policy]", $scope.policy);
+
+		// =========================================================
+		// =                      Check Logic                      =
+		// =========================================================
+		$scope.checkBasicInfo = function () {
+			return !!$scope.policy.name;
+		};
+
+		$scope.checkAlertStream = function () {
+			return $scope.checkBasicInfo() &&
+				$scope.policy.inputStreams.length > 0 &&
+				$scope.policy.outputStreams.length > 0;
+		};
+
+		$scope.checkNumber = function (str) {
+			str = (str + "").trim();
+			return str !== "" && common.number.isNumber(Number(str));
+		};
+
+		$scope.checkDefinition = function () {
+			return $scope.checkAlertStream() &&
+				!!$scope.policy.definition.value.trim() &&
+				$scope.policy.parallelismHint > 0;
+		};
+
+		// =========================================================
+		// =                        Stream                         =
+		// =========================================================
+		$scope.refreshStreamSelect = function() {
+			var appStreamList;
+
+			if(!$scope.selectedApplication) {
+				$scope.selectedApplication = common.getKeys($scope.applications)[0];
+			}
+
+			appStreamList = $scope.applications[$scope.selectedApplication] || [];
+			if(!common.array.find($scope.selectedStream, appStreamList)) {
+				$scope.selectedStream = appStreamList[0].streamId;
+			}
+			if(!common.array.find($scope.partitionStream, $scope.policy.inputStreams)) {
+				$scope.partitionStream = $scope.policy.inputStreams[0];
+			}
+		};
+
+		$scope.streamList = Entity.queryMetadata("streams");
+		$scope.streamList._then(function () {
+			$scope.applications = {};
+
+			$.each($scope.streamList, function (i, stream) {
+				var list = $scope.applications[stream.dataSource] = $scope.applications[stream.dataSource] || [];
+				list.push(stream);
+			});
+
+			console.log("=>", $scope.streamList);
+			$scope.refreshStreamSelect();
+		});
+
+		$scope.getStreamList = function () {
+			return common.array.minus($scope.streamList, $scope.policy.inputStreams, "streamId", "");
+		};
+
+		$scope.addStream = function () {
+			$scope.policy.inputStreams.push($scope.selectedStream);
+			$scope.refreshStreamSelect();
+		};
+
+		$scope.removeStream = function (streamId) {
+			$scope.policy.inputStreams = common.array.remove(streamId, $scope.policy.inputStreams);
+			$scope.refreshStreamSelect();
+		};
+
+		$scope.checkAddStream = function (streamId) {
+			return !common.array.find(streamId, $scope.policy.inputStreams);
+		};
+
+		$scope.addOutputStream = function () {
+			$scope.policy.outputStreams.push($scope.outputStream);
+			$scope.outputStream = "";
+		};
+
+		$scope.removeOutputStream = function (streamId) {
+			$scope.policy.outputStreams = common.array.remove(streamId, $scope.policy.outputStreams);
+		};
+
+		$scope.checkAddOutputStream = function () {
+			return $scope.outputStream !== "" && !common.array.find($scope.outputStream, $scope.policy.outputStreams);
+		};
+
+		// =========================================================
+		// =                      Definition                       =
+		// =========================================================
+		$scope.getPartitionColumns = function () {
+			var stream = common.array.find($scope.partitionStream, $scope.streamList, "streamId");
+			return (stream || {}).columns;
+		};
+
+		$scope.addPartition = function () {
+			$scope.policy.partitionSpec.push({
+				streamId: $scope.partitionStream,
+				type: $scope.partitionType,
+				columns: $.map($scope.getPartitionColumns(), function (column) {
+					return $scope.partitionColumns[column.name] ? column.name : null;
+				})
+			});
+
+			$scope.partitionColumns = {};
+		};
+
+		$scope.checkAddPartition = function () {
+			var match = false;
+
+			$.each($scope.getPartitionColumns(), function (i, column) {
+				if($scope.partitionColumns[column.name]) {
+					match = true;
+					return false;
+				}
+			});
+
+			return match;
+		};
+
+		$scope.removePartition = function (partition) {
+			$scope.policy.partitionSpec = common.array.remove(partition, $scope.policy.partitionSpec);
+		};
+
+		// =========================================================
+		// =                       Publisher                       =
+		// =========================================================
+		$scope.publisherList = [];
+
+		if(!$scope.newPolicy) {
+			$scope.publisherList = Entity.queryMetadata("policies/" + encodeURIComponent($scope.policy.name) + "/publishments");
+		}
+
+		$scope.addPublisher = function () {
+			var publisherProps = {};
+			$.each($scope.publisherTypes[$scope.publisherType], function (i, field) {
+				publisherProps[field] = $scope.publisherProps[field] || "";
+			});
+			$scope.publisherList.push({
+				name: $scope.publisher.name,
+				type: $scope.publisherType,
+				policyIds: [$scope.policy.name],
+				properties: publisherProps,
+				dedupIntervalMin: $scope.publisher.dedupIntervalMin,
+				serializer : "org.apache.eagle.alert.engine.publisher.impl.StringEventSerializer"
+			});
+			$scope.publisher = {
+				dedupIntervalMin: "PT1M"
+			};
+			$scope.publisherProps = {};
+		};
+
+		$scope.removePublisher = function (publisher) {
+			$scope.publisherList = common.array.remove(publisher, $scope.publisherList);
+		};
+
+		$scope.checkAddPublisher = function () {
+			return $scope.publisher.name &&
+				!common.array.find($scope.publisher.name, $scope.publisherList, "name");
+		};
+
+		// =========================================================
+		// =                         Policy                        =
+		// =========================================================
+		$scope.createPolicy = function () {
+			// TODO: Need check the policy or publisher exist.
+
+			$scope.policyLock = true;
+
+			var policyPromise = Entity.create("metadata/policies", $scope.policy)._promise;
+			var publisherPromiseList = $.map($scope.publisherList, function (publisher) {
+				return Entity.create("metadata/publishments", publisher)._promise;
+			});
+			common.deferred.all(publisherPromiseList.concat(policyPromise)).then(function () {
+				$.dialog({
+					title: "Done",
+					content: "Close dialog to go to the policy detail page."
+				}, function () {
+					$wrapState.go("policyDetail", {name: $scope.policy.name});
+				});
+			}, function (failedList) {
+				$.dialog({
+					title: "OPS",
+					content: $("<pre>").text(JSON.stringify(failedList, null, "\t"))
+				});
+				$scope.policyLock = false;
+			});
+		};
+	}
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/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 3bcd8d8..8fde4b1 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
@@ -22,9 +22,21 @@
 	var eagleControllers = angular.module('eagleControllers');
 
 	var publisherTypes = {
-		'org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher': ["subject", "template", "sender", "recipients", "mail.smtp.host", "connection", "mail.smtp.port"],
-		'org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher': ["topic", "kafka_broker", "rawAlertNamespaceLabel", "rawAlertNamespaceValue"],
-		'org.apache.eagle.alert.engine.publisher.impl.AlertSlackPublisher': ["token", "channels", "severitys", "urltemplate"]
+		'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"]
+		}
 	};
 
 	// ======================================================================================
@@ -37,17 +49,15 @@
 		policyEditController.apply(this, newArgs);
 	}
 
-	eagleControllers.controller('policyCreateCtrl', function ($scope, $wrapState, PageConfig, Entity) {
-		PageConfig.subTitle = "Define Alert Policy";
+	eagleControllers.controller('policyCreateCtrl', function ($scope, $q, $wrapState, $timeout, PageConfig, Entity) {
+		PageConfig.title = "Define Policy";
 		connectPolicyEditController({}, arguments);
 	});
-	eagleControllers.controller('policyEditCtrl', function ($scope, $wrapState, PageConfig, Entity) {
-		PageConfig.subTitle = "Edit Alert Policy";
+	eagleControllers.controller('policyEditCtrl', function ($scope, $q, $wrapState, $timeout, PageConfig, Entity) {
+		PageConfig.title = "Edit Policy";
 		var args = arguments;
 
-		// TODO: Wait for backend data update
 		$scope.policyList = Entity.queryMetadata("policies/" + encodeURIComponent($wrapState.param.name));
-
 		$scope.policyList._promise.then(function () {
 			var policy = $scope.policyList[0];
 
@@ -64,27 +74,15 @@
 		});
 	});
 
-	function policyEditController(policy, $scope, $wrapState, PageConfig, Entity) {
-		$scope.newPolicy = !policy.name;
-		$scope.policyLock = false;
-
+	function policyEditController(policy, $scope, $q, $wrapState, $timeout, PageConfig, Entity) {
 		$scope.publisherTypes = publisherTypes;
 
-		$scope.selectedApplication = null;
-		$scope.selectedStream = null;
-
-		$scope.outputStream = "";
-
-		$scope.partitionStream = null;
-		$scope.partitionType = "GROUPBY";
-		$scope.partitionColumns = {};
-
-		$scope.publisherType = "org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher";
-		$scope.publisher = {
-			dedupIntervalMin: "PT1M"
-		};
-		$scope.publisherProps = {};
+		PageConfig.navPath = [
+			{title: "Policy List", path: "/alert/policyList"},
+			{title: "Policy"}
+		];
 
+		$scope.policy = policy;
 		$scope.policy = common.merge({
 			name: "",
 			description: "",
@@ -95,202 +93,302 @@
 				value: ""
 			},
 			partitionSpec: [],
-			parallelismHint: 2
-		}, policy);
-
-		$scope.policy.definition = {
-			type: $scope.policy.definition.type,
-			value: $scope.policy.definition.value
-		};
-
+			parallelismHint: 5
+		}, $scope.policy);
 		console.log("[Policy]", $scope.policy);
 
-		// =========================================================
-		// =                      Check Logic                      =
-		// =========================================================
-		$scope.checkBasicInfo = function () {
-			return !!$scope.policy.name;
-		};
-
-		$scope.checkAlertStream = function () {
-			return $scope.checkBasicInfo() &&
-				$scope.policy.inputStreams.length > 0 &&
-				$scope.policy.outputStreams.length > 0;
-		};
-
-		$scope.checkNumber = function (str) {
-			str = (str + "").trim();
-			return str !== "" && common.number.isNumber(Number(str));
-		};
-
-		$scope.checkDefinition = function () {
-			return $scope.checkAlertStream() &&
-				!!$scope.policy.definition.value.trim() &&
-				$scope.policy.parallelismHint > 0;
+		var cacheSearchSourceKey;
+		var searchApplications;
+		$scope.searchSourceKey = "";
+		$scope.applications = {};
+		$scope.newPolicy = !!$scope.policy.name;
+
+		// ==============================================================
+		// =                             UI                             =
+		// ==============================================================
+		$scope.sourceTab = "all";
+		$scope.setSourceTab = function (tab) {
+			$scope.sourceTab = tab;
 		};
 
-		// =========================================================
-		// =                        Stream                         =
-		// =========================================================
-		$scope.refreshStreamSelect = function() {
-			var appStreamList;
-
-			if(!$scope.selectedApplication) {
-				$scope.selectedApplication = common.getKeys($scope.applications)[0];
-			}
-
-			appStreamList = $scope.applications[$scope.selectedApplication] || [];
-			if(!common.array.find($scope.selectedStream, appStreamList)) {
-				$scope.selectedStream = appStreamList[0].streamId;
-			}
-			if(!common.array.find($scope.partitionStream, $scope.policy.inputStreams)) {
-				$scope.partitionStream = $scope.policy.inputStreams[0];
+		// ==============================================================
+		// =                        Input Stream                        =
+		// ==============================================================
+		$scope.getSearchApplication = function() {
+			if(cacheSearchSourceKey !== $scope.searchSourceKey.toUpperCase()) {
+				cacheSearchSourceKey = $scope.searchSourceKey.toUpperCase();
+
+				searchApplications = {};
+				$.each($scope.applications, function (appName, streams) {
+					if(appName.toUpperCase().indexOf(cacheSearchSourceKey) >= 0) {
+						searchApplications[appName] = streams;
+					} else {
+						var streamList = [];
+						$.each(streams, function (i, stream) {
+							if(stream.streamId.toUpperCase().indexOf(cacheSearchSourceKey) >= 0) {
+								streamList.push(stream);
+							}
+						});
+
+						if(streamList.length > 0) {
+							searchApplications[appName] = streamList;
+						}
+					}
+				});
 			}
+			return searchApplications;
 		};
 
+		$scope.streams = {};
 		$scope.streamList = Entity.queryMetadata("streams");
 		$scope.streamList._then(function () {
 			$scope.applications = {};
+			cacheSearchSourceKey = null;
 
 			$.each($scope.streamList, function (i, stream) {
 				var list = $scope.applications[stream.dataSource] = $scope.applications[stream.dataSource] || [];
 				list.push(stream);
+				$scope.streams[stream.streamId] = stream;
 			});
-
-			console.log("=>", $scope.streamList);
-			$scope.refreshStreamSelect();
 		});
 
-		$scope.getStreamList = function () {
-			return common.array.minus($scope.streamList, $scope.policy.inputStreams, "streamId", "");
+		$scope.isInputStreamSelected = function (streamId) {
+			return $.inArray(streamId, $scope.policy.inputStreams) >= 0;
 		};
 
-		$scope.addStream = function () {
-			$scope.policy.inputStreams.push($scope.selectedStream);
-			$scope.refreshStreamSelect();
-		};
+		/*$scope.checkInputStream = function (streamId) {
+			if($scope.isInputStreamSelected(streamId)) {
+				$scope.policy.inputStreams = common.array.remove(streamId, $scope.policy.inputStreams);
+			} else {
+				$scope.policy.inputStreams.push(streamId);
+			}
+		};*/
 
-		$scope.removeStream = function (streamId) {
-			$scope.policy.inputStreams = common.array.remove(streamId, $scope.policy.inputStreams);
-			$scope.refreshStreamSelect();
+		// ==============================================================
+		// =                         Definition                         =
+		// ==============================================================
+		var checkPromise;
+		$scope.definitionMessage = "";
+		$scope.checkDefinition = function () {
+			$timeout.cancel(checkPromise);
+			checkPromise = $timeout(function () {
+				Entity.post("metadata/policies/parse", $scope.policy.definition.value)._then(function (res) {
+					var data = res.data;
+					console.log(data);
+					if(data.success) {
+						$scope.definitionMessage = "";
+						if(data.policyExecutionPlan) {
+							// Input streams
+							$scope.policy.inputStreams = $.map(data.policyExecutionPlan.inputStreams, function (value, stream) {
+								return stream;
+							});
+
+							// Output streams
+							var outputStreams= $.map(data.policyExecutionPlan.outputStreams, function (value, stream) {
+								return stream;
+							});
+							$scope.policy.outputStreams = outputStreams.concat();
+							$scope.outputStreams = outputStreams;
+
+							// Partition
+							$scope.policy.partitionSpec = data.policyExecutionPlan.streamPartitions;
+						}
+					} else {
+						$scope.definitionMessage = data.message;
+					}
+				});
+			}, 350);
 		};
 
-		$scope.checkAddStream = function (streamId) {
-			return !common.array.find(streamId, $scope.policy.inputStreams);
-		};
+		// ==============================================================
+		// =                        Output Stream                       =
+		// ==============================================================
+		$scope.outputStreams = ($scope.policy.outputStreams || []).concat();
 
-		$scope.addOutputStream = function () {
-			$scope.policy.outputStreams.push($scope.outputStream);
-			$scope.outputStream = "";
+		$scope.isOutputStreamSelected = function (streamId) {
+			return $.inArray(streamId, $scope.policy.outputStreams) >= 0;
 		};
 
-		$scope.removeOutputStream = function (streamId) {
-			$scope.policy.outputStreams = common.array.remove(streamId, $scope.policy.outputStreams);
+		$scope.checkOutputStream = function (streamId) {
+			if($scope.isOutputStreamSelected(streamId)) {
+				$scope.policy.outputStreams = common.array.remove(streamId, $scope.policy.outputStreams);
+			} else {
+				$scope.policy.outputStreams.push(streamId);
+			}
 		};
 
-		$scope.checkAddOutputStream = function () {
-			return $scope.outputStream !== "" && !common.array.find($scope.outputStream, $scope.policy.outputStreams);
-		};
-
-		// =========================================================
-		// =                      Definition                       =
-		// =========================================================
-		$scope.getPartitionColumns = function () {
-			var stream = common.array.find($scope.partitionStream, $scope.streamList, "streamId");
-			return (stream || {}).columns;
-		};
+		// ==============================================================
+		// =                         Partition                          =
+		// ==============================================================
+		$scope.partition = {};
 
 		$scope.addPartition = function () {
-			$scope.policy.partitionSpec.push({
-				streamId: $scope.partitionStream,
-				type: $scope.partitionType,
-				columns: $.map($scope.getPartitionColumns(), function (column) {
-					return $scope.partitionColumns[column.name] ? column.name : null;
-				})
-			});
-
-			$scope.partitionColumns = {};
+			$scope.partition = {
+				streamId: $scope.policy.inputStreams[0],
+				type: "GROUPBY",
+				columns: []
+			};
+			$(".modal[data-id='partitionMDL']").modal();
 		};
 
-		$scope.checkAddPartition = function () {
-			var match = false;
+		$scope.newPartitionCheckColumn = function (column) {
+			return $.inArray(column, $scope.partition.columns) >= 0;
+		};
 
-			$.each($scope.getPartitionColumns(), function (i, column) {
-				if($scope.partitionColumns[column.name]) {
-					match = true;
-					return false;
-				}
-			});
+		$scope.newPartitionClickColumn = function (column) {
+			if($scope.newPartitionCheckColumn(column)) {
+				$scope.partition.columns = common.array.remove(column, $scope.partition.columns);
+			} else {
+				$scope.partition.columns.push(column);
+			}
+		};
 
-			return match;
+		$scope.addPartitionConfirm = function () {
+			$scope.policy.partitionSpec.push($scope.partition);
 		};
 
 		$scope.removePartition = function (partition) {
 			$scope.policy.partitionSpec = common.array.remove(partition, $scope.policy.partitionSpec);
 		};
 
-		// =========================================================
-		// =                       Publisher                       =
-		// =========================================================
-		$scope.publisherList = [];
-
-		if(!$scope.newPolicy) {
-			$scope.publisherList = Entity.queryMetadata("policies/" + encodeURIComponent($scope.policy.name) + "/publishments");
-		}
+		// ==============================================================
+		// =                         Publisher                          =
+		// ==============================================================
+		$scope.publisherList = Entity.queryMetadata("publishments");
+		$scope.addPublisherType = "";
+		$scope.policyPublisherList = [];
+		$scope.publisher = {};
+
+		Entity.queryMetadata("policies/" + $scope.policy.name + "/publishments/")._then(function (res) {
+			$scope.policyPublisherList = $.map(res.data, function (publisher) {
+				return $.extend({
+					_exist: true
+				}, publisher);
+			});
+		});
 
 		$scope.addPublisher = function () {
-			var publisherProps = {};
-			$.each($scope.publisherTypes[$scope.publisherType], function (i, field) {
-				publisherProps[field] = $scope.publisherProps[field] || "";
-			});
-			$scope.publisherList.push({
-				name: $scope.publisher.name,
-				type: $scope.publisherType,
-				policyIds: [$scope.policy.name],
-				properties: publisherProps,
-				dedupIntervalMin: $scope.publisher.dedupIntervalMin,
-				serializer : "org.apache.eagle.alert.engine.publisher.impl.StringEventSerializer"
-			});
 			$scope.publisher = {
-				dedupIntervalMin: "PT1M"
+				existPublisher: $scope.publisherList[0],
+				type: "org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher",
+				dedupIntervalMin: "PT1M",
+				serializer : "org.apache.eagle.alert.engine.publisher.impl.StringEventSerializer",
+				properties: {}
 			};
-			$scope.publisherProps = {};
+			if($scope.publisherList.length) {
+				$scope.addPublisherType = "exist";
+			} else {
+				$scope.addPublisherType = "new";
+			}
+
+			$(".modal[data-id='publisherMDL']").modal();
 		};
 
 		$scope.removePublisher = function (publisher) {
-			$scope.publisherList = common.array.remove(publisher, $scope.publisherList);
+			$scope.policyPublisherList = common.array.remove(publisher, $scope.policyPublisherList);
+		};
+
+		$scope.checkPublisherName = function () {
+			if(common.array.find($scope.publisher.name, $scope.publisherList.concat($scope.policyPublisherList), ["name"])) {
+				return "'" + $scope.publisher.name + "' already exist";
+			}
+			return false;
+		};
+
+		$scope.addPublisherConfirm = function () {
+			if($scope.addPublisherType === "exist") {
+				$scope.publisher = $.extend({
+					_exist: true
+				}, $scope.publisher.existPublisher);
+			}
+			var properties = {};
+			$.each($scope.publisherTypes[$scope.publisher.type].fields, function (i, field) {
+				properties[field] = $scope.publisher.properties[field] || "";
+			});
+			$scope.policyPublisherList.push($.extend({}, $scope.publisher, {properties: properties}));
 		};
 
-		$scope.checkAddPublisher = function () {
-			return $scope.publisher.name &&
-				!common.array.find($scope.publisher.name, $scope.publisherList, "name");
+		// ==============================================================
+		// =                            Save                            =
+		// ==============================================================
+		$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;
+			return true;
 		};
 
-		// =========================================================
-		// =                         Policy                        =
-		// =========================================================
-		$scope.createPolicy = function () {
-			// TODO: Need check the policy or publisher exist.
+		$scope.saveConfirm = function () {
+			$scope.saveLock = true;
+
+			// Check policy
+			Entity.post("metadata/policies/validate", $scope.policy)._then(function (res) {
+				var validate = res.data;
+				console.log(validate);
+				if(!validate.success) {
+					$.dialog({
+						title: "OPS",
+						content: validate.message
+					});
+					$scope.saveLock = false;
+					return;
+				}
 
-			$scope.policyLock = true;
+				// Create publisher
+				var publisherPromiseList = $.map($scope.policyPublisherList, function (publisher) {
+					if(publisher._exist) return;
 
-			var policyPromise = Entity.create("metadata/policies", $scope.policy)._promise;
-			var publisherPromiseList = $.map($scope.publisherList, function (publisher) {
-				return Entity.create("metadata/publishments", publisher)._promise;
-			});
-			common.deferred.all(publisherPromiseList.concat(policyPromise)).then(function () {
-				$.dialog({
-					title: "Done",
-					content: "Close dialog to go to the policy detail page."
-				}, function () {
-					$wrapState.go("policyDetail", {name: $scope.policy.name});
+					return Entity.create("metadata/publishments", publisher)._promise;
 				});
-			}, function (failedList) {
-				$.dialog({
-					title: "OPS",
-					content: $("<pre>").text(JSON.stringify(failedList, null, "\t"))
+				if(publisherPromiseList.length) console.log("Creating publishers...", $scope.policyPublisherList);
+
+				$q.all(publisherPromiseList).then(function () {
+					console.log("Create publishers success...");
+
+					// Create policy
+					Entity.create("metadata/policies", $scope.policy)._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});
+							});
+						}, function (res) {
+							// Link Failed
+							$.dialog({
+								title: "OPS",
+								content: "Link publishers failed:" + res.data.message
+							});
+						}).finally(function () {
+							$scope.policyLock = false;
+						});
+					}, function (res) {
+						$.dialog({
+							title: "OPS",
+							content: "Create policy failed: " + res.data.message
+						});
+						$scope.policyLock = false;
+					});
+				}, function (args) {
+					$.dialog({
+						title: "OPS",
+						content: "Create publisher failed. More detail please check output in 'console'."
+					});
+					console.log("Create publishers failed:", args);
+					$scope.policyLock = false;
 				});
-				$scope.policyLock = false;
+			}, function () {
+				$scope.saveLock = false;
 			});
 		};
 	}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1f5126a1/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
----------------------------------------------------------------------
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 faea8ba..6327e22 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
@@ -53,7 +53,7 @@
 		var defaultPortalList = [
 			{name: "Home", icon: "home", path: "#/"},
 			{name: "Alert", icon: "bell", showFunc: checkApplication, list: [
-				{name: "Explore Alerts", path: "#/alert/"},
+				// {name: "Explore Alerts", path: "#/alert/"},
 				{name: "Policies", path: "#/alert/policyList"},
 				{name: "Streams", path: "#/alert/streamList"},
 				{name: "Define Policy", path: "#/alert/policyCreate"}