eagle-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From h..@apache.org
Subject incubator-eagle git commit: [EAGLE-577] UI policy management
Date Mon, 10 Oct 2016 09:26:17 GMT
Repository: incubator-eagle
Updated Branches:
  refs/heads/master d9b82b45d -> 4e5641c35


[EAGLE-577] UI policy management

For 0.5 api, policy management api has been update. UI also need fit for that.

Author: zombieJ <smith3816@gmail.com>

Closes #483 from zombieJ/EAGLE-577.


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

Branch: refs/heads/master
Commit: 4e5641c356b4e5ae4dad1d7d012bd418fc7b408d
Parents: d9b82b4
Author: zombieJ <smith3816@gmail.com>
Authored: Mon Oct 10 17:26:07 2016 +0800
Committer: Hao Chen <hao@apache.org>
Committed: Mon Oct 10 17:26:07 2016 +0800

----------------------------------------------------------------------
 eagle-server/src/main/webapp/app/dev/index.html |   7 +-
 .../app/dev/partials/alert/policyDetail.html    | 111 +++++++
 .../app/dev/partials/alert/policyEdit.html      | 219 +++++++++++++-
 .../app/dev/partials/alert/policyList.html      | 126 ++++----
 .../src/main/webapp/app/dev/public/css/main.css |  34 +++
 .../src/main/webapp/app/dev/public/js/app.js    |  10 +-
 .../src/main/webapp/app/dev/public/js/common.js |  22 +-
 .../webapp/app/dev/public/js/ctrls/alertCtrl.js |  48 +--
 .../app/dev/public/js/ctrls/alertEditCtrl.js    | 295 +++++++++++++++++++
 .../app/dev/public/js/ctrls/integrationCtrl.js  |   6 +-
 .../app/dev/public/js/services/entitySrv.js     |   8 +-
 .../webapp/app/dev/public/js/services/uiSrv.js  |  74 +++--
 eagle-server/src/main/webapp/app/package.json   |   2 +-
 13 files changed, 833 insertions(+), 129 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 56850d7..19ea204 100644
--- a/eagle-server/src/main/webapp/app/dev/index.html
+++ b/eagle-server/src/main/webapp/app/dev/index.html
@@ -134,7 +134,11 @@
 
 					<ol class="breadcrumb">
 						<li ng-repeat="navPath in PageConfig.navPath">
-							<a ng-href="#{{navPath.path}}">
+							<span ng-if="!navPath.path">
+								<span class="fa fa-home" ng-if="$first"></span>
+								{{navPath.title || navPath.path}}
+							</span>
+							<a ng-if="navPath.path" ng-href="#{{navPath.path}}">
 								<span class="fa fa-home" ng-if="$first"></span>
 								{{navPath.title || navPath.path}}
 							</a>
@@ -243,6 +247,7 @@
 		<script src="public/js/ctrls/main.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/ctrls/mainCtrl.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/ctrls/alertCtrl.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrls/alertEditCtrl.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/ctrls/integrationCtrl.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/ctrls/siteCtrl.js" type="text/javascript" charset="utf-8"></script>
 		<!-- endref -->

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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
new file mode 100644
index 0000000..b04a8a8
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyDetail.html
@@ -0,0 +1,111 @@
+<!--
+  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="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active"><a href="#policy" data-toggle="tab">Policy</a></li>
+		<li><a href="#publisher" data-toggle="tab">Publisher</a></li>
+
+		<li class="pull-right">
+			<a>
+				<span class="label" ng-class="{'label-default': policy.policyStatus !== 'ENABLED', 'label-success': policy.policyStatus === 'ENABLED'}">
+					{{policy.policyStatus}}
+				</span>
+			</a>
+		</li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" id="policy">
+			<table class="table">
+				<tbody>
+					<tr>
+						<th>Name</th>
+						<td>{{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>
+					</tr>
+					<tr>
+						<th>Input Streams</th>
+						<td>
+							<ul class="no-margin">
+								<li ng-repeat="stream in policy.inputStreams track by $index">
+									{{stream}}
+								</li>
+							</ul>
+						</td>
+						<th>Output Streams</th>
+						<td>
+							<ul class="no-margin">
+								<li ng-repeat="stream in policy.outputStreams track by $index">
+									{{stream}}
+								</li>
+							</ul>
+						</td>
+					</tr>
+					<tr>
+						<th>Definition</th>
+						<td colspan="3"><pre class="inline">{{policy.definition.value}}</pre></td>
+					</tr>
+					<tr>
+						<th>Partition</th>
+						<td colspan="3">
+							<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>
+							</ul>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+		<div class="tab-pane" id="publisher">
+			<table class="table table-bordered" ng-repeat="publisher in publisherList track by publisher.name">
+				<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>
+					</tr>
+					<tr>
+						<th>Type</th>
+						<td colspan="3">{{publisher.type}}</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>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
index 9a1cbe4..e7fcea9 100644
--- a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
@@ -17,13 +17,222 @@
   -->
 
 <div class="box-body">
-	<ul class="stepGuide">
-		<li>
-			<span class="icon bg-green">1</span>
-			<span class="title">This is the title!!!</span>
+	<ul class="timeline">
+		<!-- Base Info -->
+		<li class="time-label">
+			<span class="bg-blue">#1. Basic Information</span>
 		</li>
 		<li>
-			<span class="icon">2</span>
+			<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" ng-readonly="!newPolicy" />
+					</div>
+					<div class="form-group">
+						<label>Description <small class="text-muted">[Optional]</small></label>
+						<textarea class="form-control" ng-model="policy.description" rows="3"></textarea>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Alert Stream -->
+		<li class="time-label" ng-class="{disabled: !checkBasicInfo()}">
+			<span class="bg-blue">#2. Alert Stream</span>
+		</li>
+		<li ng-class="{disabled: !checkBasicInfo()}">
+			<span class="fa fa-rocket bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<label>Input Stream</label>
+					<ul class="list-unstyled with-margin">
+						<li ng-repeat="stream in policy.inputStreams track by $index">
+							[<a class="fa fa-times" ng-click="removeStream(stream)"></a>] {{stream}}
+						</li>
+						<li ng-if="policy.inputStreams.length === 0"><span class="fa fa-ban"></span> (empty list)</li>
+					</ul>
+
+					<div class="inner-block">
+						<div class="form-group">
+							<label>Application</label>
+							<select class="form-control" ng-model="selectedApplication" ng-change="refreshStreamSelect()">
+								<option ng-repeat="(app, list) in applications track by $index" value="{{app}}">
+									{{app}}
+								</option>
+							</select>
+						</div>
+						<div class="form-group">
+							<label>Stream</label>
+							<select class="form-control" ng-model="selectedStream">
+								<option ng-repeat="stream in applications[selectedApplication] track by $index" value="{{stream.streamId}}">
+									[{{stream.siteId}}]
+									{{stream.streamId}}
+									{{checkAddStream(stream.streamId) ? '' : '(Already Added)'}}
+								</option>
+							</select>
+						</div>
+						<button class="btn btn-success" ng-click="addStream()" ng-disabled="!checkAddStream(selectedStream)">
+							<span class="fa fa-plus"></span> Add Stream
+						</button>
+					</div>
+
+
+					<label>Output Stream</label>
+					<ul class="list-unstyled with-margin">
+						<li ng-repeat="stream in policy.outputStreams track by $index">
+							[<a class="fa fa-times" ng-click="removeOutputStream(stream)"></a>] {{stream}}
+						</li>
+						<li ng-if="policy.outputStreams.length === 0"><span class="fa fa-ban"></span> (empty list)</li>
+					</ul>
+					<div class="inner-block">
+						<div class="form-group">
+							<input type="text" class="form-control" ng-model="outputStream" />
+						</div>
+						<button class="btn btn-success" ng-click="addOutputStream()" ng-disabled="!checkAddOutputStream()">
+							<span class="fa fa-plus"></span> Add Stream
+						</button>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Definition -->
+		<li class="time-label" ng-class="{disabled: !checkAlertStream()}">
+			<span class="bg-blue">#3. Definition</span>
+		</li>
+		<li ng-class="{disabled: !checkAlertStream()}">
+			<span class="fa fa-pencil bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<div class="form-group">
+						<label>Definition</label>
+						<textarea class="form-control" ng-model="policy.definition.value" rows="8"></textarea>
+					</div>
+
+					<div class="form-group" ng-class="{'has-warning': !checkNumber(policy.parallelismHint)}">
+						<label>
+							Parallelism Hint
+							<small class="text-muted" ng-if="!checkNumber(policy.parallelismHint)">- Number Only</small>
+						</label>
+						<input type="number" class="form-control" ng-model="policy.parallelismHint" />
+					</div>
+
+					<label>Partition <small class="text-muted">[Optional]</small></label>
+					<ul class="list-unstyled with-margin">
+						<li ng-repeat="partition in policy.partitionSpec track by $index">
+							[<a class="fa fa-times" ng-click="removePartition(partition)"></a>]
+							[<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"><span class="fa fa-ban"></span> (empty list)</li>
+					</ul>
+
+					<div class="inner-block">
+						<div class="form-group">
+							<label>Stream</label>
+							<select class="form-control" ng-model="partitionStream">
+								<option ng-repeat="stream in policy.inputStreams track by $index" value="{{stream}}">
+									{{stream}}
+								</option>
+							</select>
+						</div>
+						<div class="form-group">
+							<label>Type</label>
+							<select class="form-control" ng-model="partitionType">
+								<option>GROUPBY</option>
+								<option>GLOBAL</option>
+								<option>SHUFFLE</option>
+							</select>
+						</div>
+						<div class="form-group">
+							<label>Columns</label>
+							<div>
+								<label class="checkbox-inline" ng-repeat="column in getPartitionColumns() track by $index">
+									<input type="checkbox"
+										   ng-checked="partitionColumns[column.name]"
+										   ng-click="partitionColumns[column.name] = !partitionColumns[column.name]"
+									/>{{column.name}}
+								</label>
+							</div>
+						</div>
+
+						<button class="btn btn-success" ng-click="addPartition()" ng-disabled="!checkAddPartition()">
+							<span class="fa fa-plus"></span> Add Partition
+						</button>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Publisher Configuration -->
+		<li class="time-label" ng-class="{disabled: !checkDefinition()}">
+			<span class="bg-blue">#4. Publisher Configuration</span>
+		</li>
+		<li ng-class="{disabled: !checkDefinition()}">
+			<span class="fa fa-envelope bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<label>Publisher</label>
+					<ul class="list-unstyled with-margin block-list">
+						<li ng-repeat="publisher in publisherList track by publisher.name">
+							<a class="pull-right fa fa-times" ng-click="removePublisher(publisher)"></a>
+							<p>
+								<strong>Name:</strong>
+								{{publisher.name}}
+							</p>
+							<p>
+								<strong>Type:</strong>
+								{{publisher.type}}
+							</p>
+							<p>
+								<strong>DeDup-Interval Min:</strong>
+								{{publisher.dedupIntervalMin}}
+							</p>
+							<p ng-repeat="field in publisherTypes[publisher.type] track by $index">
+								<strong>{{field}}:</strong>
+								{{publisher.properties[field]}}
+							</p>
+						</li>
+						<li ng-if="publisherList.length === 0" class="no-decorate"><span class="fa fa-ban"></span> (empty list)</li>
+					</ul>
+
+					<div class="inner-block">
+						<div class="form-group">
+							<label>Name</label>
+							<input type="text" class="form-control" ng-model="publisher.name" />
+						</div>
+						<div class="form-group">
+							<label>DeDup-Interval Min</label>
+							<input type="text" class="form-control" ng-model="publisher.dedupIntervalMin" />
+						</div>
+						<div class="form-group">
+							<label>Type</label>
+							<select class="form-control" ng-model="publisherType">
+								<option ng-repeat="(type, fields) in publisherTypes track by $index"
+										value="{{type}}">{{type}}</option>
+							</select>
+						</div>
+						<div class="form-group" ng-repeat="field in publisherTypes[publisherType] track by $index">
+							<label>{{field}}</label>
+							<input type="text" class="form-control" ng-model="publisherProps[field]" />
+						</div>
+						<button class="btn btn-success" ng-click="addPublisher()" ng-disabled="!checkAddPublisher()">
+							<span class="fa fa-plus"></span> Add Publisher
+						</button>
+					</div>
+				</div>
+			</div>
 		</li>
 	</ul>
+
+	<pre class="hide">{{policy | json : "\t"}}</pre>
+</div>
+
+<div class="box-footer text-right">
+	<button class="btn btn-primary" ng-disabled="!checkDefinition() || policyLock" ng-click="createPolicy()">
+		{{newPolicy ? 'New' : 'Update'}} Policy
+	</button>
 </div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 2d4703f..49d8ab8 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
@@ -1,63 +1,63 @@
-<!--
-  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-solid">
-	<div class="box-body">
-		<div sort-table="policyList" ng-show="policyList.length">
-			<table class="table table-bordered">
-				<thead>
-					<tr>
-						<th sortpath="name" width="20%">Name</th>
-						<th sortpath="definition.type" width="70">Type</th>
-						<th>Description</th>
-						<th width="85">Action</th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr>
-						<td>
-							<a ng-href="#/alert/policyEdit/{{item.name}}">{{item.name}}</a>
-						</td>
-						<td class="text-center"><span class="label label-primary">{{item.definition.type}}</span></td>
-						<td>{{item.description}}</td>
-						<td class="text-center">
-							<div class="btn-group btn-group-xs">
-								<button class="btn btn-default"><span class="fa fa-play"></span></button>
-								<button class="btn btn-default"><span class="fa fa-pencil"></span></button>
-								<button class="btn btn-danger" ng-click="deletePolicy(item)"><span class="fa fa-trash"></span></button>
-							</div>
-						</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-
-		<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>
-		</div>
-	</div>
-
-	<div class="overlay" ng-if="!policyList._done">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-
-	<div class="box-footer text-right">
-		<a href="#/alert/policyCreate" class="btn btn-primary">New Policy</a>
-	</div>
-</div>
\ No newline at end of file
+<!--
+  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-solid">
+	<div class="box-body">
+		<div sort-table="policyList" ng-show="policyList.length">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th sortpath="name" width="20%">Name</th>
+						<th sortpath="definition.type" width="70">Type</th>
+						<th>Description</th>
+						<th width="85">Action</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td>
+							<a ui-sref="policyDetail({name: item.name})" target="_blank">{{item.name}}</a>
+						</td>
+						<td class="text-center"><span class="label label-primary">{{item.definition.type}}</span></td>
+						<td>{{item.description}}</td>
+						<td class="text-center">
+							<div class="btn-group btn-group-xs">
+								<button class="btn btn-default"><span class="fa fa-play"></span></button>
+								<a ui-sref="alert.policyEdit({name: item.name})" target="_blank" class="btn btn-default"><span class="fa fa-pencil"></span></a>
+								<button class="btn btn-danger" ng-click="deletePolicy(item)"><span class="fa fa-trash"></span></button>
+							</div>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+
+		<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>
+		</div>
+	</div>
+
+	<div class="overlay" ng-if="!policyList._done">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+
+	<div class="box-footer text-right">
+		<a href="#/alert/policyCreate" class="btn btn-primary">New Policy</a>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 83f9b14..07482e1 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
@@ -126,6 +126,8 @@ table.table pre.inline {
 	border: 0;
 	border-radius: 0;
 	background: transparent;
+	font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+	font-size: 14px;
 }
 
 table.table.table-sm th,
@@ -270,10 +272,42 @@ ul.stepGuide li > .title {
 /* ========================================================================
  * =                               Timeline                               =
  * ======================================================================== */
+.nav-tabs-custom .timeline li.disabled {
+	opacity: .5;
+	pointer-events: none;
+}
+
 .nav-tabs-custom .timeline li .timeline-item {
 	background: #f4f4f4;
 }
 
+.nav-tabs-custom .timeline li .timeline-item ul.with-margin {
+	margin: 0 0 10px 15px;
+}
+
+.nav-tabs-custom .timeline li .timeline-item .inner-block {
+	border: 1px solid #CCC;
+	padding: 10px;
+	margin: 0 0 15px 15px;
+}
+
+.nav-tabs-custom .timeline li .timeline-item ul.block-list li {
+	margin-bottom: 10px;
+}
+.nav-tabs-custom .timeline li .timeline-item ul.block-list li:last-child {
+	margin-bottom: 0;
+}
+
+.nav-tabs-custom .timeline li .timeline-item ul.block-list li:not(.no-decorate) {
+	background: #DEDEDE;
+	padding: 10px;
+	line-height: 150%;
+}
+
+.nav-tabs-custom .timeline li .timeline-item ul.block-list li:not(.no-decorate) p {
+	margin: 0;
+}
+
 /* ========================================================================
  * =                                Widget                                =
  * ======================================================================== */

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 4ec77d9..fb40ed4 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
@@ -115,6 +115,13 @@ var app = {};
 					controller: "policyEditCtrl",
 					resolve: routeResolve()
 				})
+
+				.state('policyDetail', {
+					url: "/policyDetail/{name}",
+					templateUrl: "partials/alert/policyDetail.html?_=" + window._TRS(),
+					controller: "policyDetailCtrl",
+					resolve: routeResolve()
+				})
 				// =============================== Integration ==============================
 				.state('integration', {
 					abstract: true,
@@ -229,7 +236,8 @@ var app = {};
 
 			Object.defineProperty(window, "scope", {
 				get: function () {
-					return angular.element("#content .ng-scope").scope();
+					var ele = $("#content .nav-tabs-custom.ng-scope .ng-scope[ui-view], #content .ng-scope[ui-view]").last();
+					return angular.element(ele).scope();
 				}
 			});
 

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 9f5c4b1..0e9cc7c 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
@@ -154,6 +154,12 @@
 		return mergedObj;
 	};
 
+	common.getKeys = function (obj) {
+		return $.map(obj, function (val, key) {
+			return key;
+		});
+	};
+
 	// ============================ String ============================
 	common.string = {};
 	common.string.safeText = function (str) {
@@ -223,6 +229,12 @@
 		return list;
 	};
 
+	common.array.remove = function (val, list, path) {
+		return $.grep(list, function (obj) {
+			return common.getValueByPath(obj, path) !== val;
+		});
+	};
+
 	common.array.doSort = function (list, path, asc, sortList) {
 		var sortFunc;
 		sortList = sortList || [];
@@ -281,7 +293,6 @@
 	// =========================== Deferred ===========================
 	common.deferred = {};
 
-
 	common.deferred.all = function (deferredList) {
 		var deferred = $.Deferred();
 		var successList = [];
@@ -301,12 +312,17 @@
 
 		$.each(deferredList, function (i, deferred) {
 			if(deferred && deferred.then) {
-				deferred.then(function (data) {
+				var promise = deferred.then(function (data) {
 					successList[i] = data;
 				}, function (data) {
 					failureList[i] = data;
 					hasFailure = true;
-				}).always(doCheck);
+				});
+				if(promise.always) {
+					promise.always(doCheck);
+				} else if(promise.finally) {
+					promise.finally(doCheck);
+				}
 			} else {
 				successList[i] = deferred;
 				doCheck();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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 17ce775..ae5194c 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
@@ -51,14 +51,14 @@
 	});
 
 	// ======================================================================================
-	// =                                        List                                        =
+	// =                                        Alert                                       =
 	// ======================================================================================
 	eagleControllers.controller('alertListCtrl', function ($scope, $wrapState, PageConfig) {
 		PageConfig.subTitle = "Explore Alerts";
 	});
 
 	// ======================================================================================
-	// =                                     Policy List                                    =
+	// =                                       Policy                                       =
 	// ======================================================================================
 	eagleControllers.controller('policyListCtrl', function ($scope, $wrapState, PageConfig, Entity, UI) {
 		PageConfig.subTitle = "Manage Policies";
@@ -75,45 +75,29 @@
 		};
 	});
 
-	// ======================================================================================
-	// =                                    Policy Create                                   =
-	// ======================================================================================
-	function connectPolicyEditController(entity, args) {
-		var newArgs = [entity];
-		Array.prototype.push.apply(newArgs, args);
-		/* jshint validthis: true */
-		policyEditController.apply(this, newArgs);
-	}
-	function policyEditController(policy, $scope, $wrapState, PageConfig, Entity) {
-		$scope.policy = policy;
-	}
+	eagleControllers.controller('policyDetailCtrl', function ($scope, $wrapState, PageConfig, Entity, UI) {
+		PageConfig.title = $wrapState.param.name;
+		PageConfig.subTitle = "Detail";
+		PageConfig.navPath = [
+			{title: "Policy List", path: "/alert/policyList"},
+			{title: "Detail"}
+		];
 
-	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;
+		var policyList = Entity.queryMetadata("policies/" + encodeURIComponent($wrapState.param.name));
+		policyList._promise.then(function () {
+			$scope.policy = policyList[0];
+			console.log("[Policy]", $scope.policy);
 
-		// TODO: Wait for backend data update
-		$scope.policyList = Entity.queryMetadata("policies");
-		$scope.policyList._promise.then(function () {
-			var policy = $scope.policyList.find(function (entity) {
-				return entity.name === $wrapState.param.name;
-			});
-
-			if(policy) {
-				connectPolicyEditController(policy, args);
-			} else {
+			if(!$scope.policy) {
 				$.dialog({
 					title: "OPS",
 					content: "Policy '" + $wrapState.param.name + "' not found!"
 				}, function () {
 					$wrapState.go("alert.policyList");
 				});
+			} else {
+				$scope.publisherList = Entity.queryMetadata("policies/" + encodeURIComponent($scope.policy.name) + "/publishments");
 			}
 		});
-
 	});
 }());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/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
new file mode 100644
index 0000000..05a47d4
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertEditCtrl.js
@@ -0,0 +1,295 @@
+/*
+ * 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: "Create Success",
+					content: "Create Success. Click confirm to go to the policy detail page."
+				});
+			}, function (failedList) {
+				$.dialog({
+					title: "Create Failed",
+					content: $("<pre>").text(JSON.stringify(failedList, null, "\t"))
+				});
+				$scope.policyLock = false;
+			});
+		};
+	}
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
index a807520..eb463ba 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
@@ -147,7 +147,8 @@
 			});
 
 			UI.fieldConfirm({
-				title: "Install '" + application.type + "'"
+				title: "Install '" + application.type + "'",
+				addable: true
 			}, null, fields)(function (entity, closeFunc, unlock) {
 				Entity.create("apps/install", {
 					siteId: $scope.site.siteId,
@@ -196,6 +197,9 @@
 	// =                                     Application                                    =
 	// ======================================================================================
 	eagleControllers.controller('integrationApplicationListCtrl', function ($sce, $scope, $wrapState, PageConfig, Application) {
+		PageConfig.title = "Integration";
+		PageConfig.subTitle = "Applications";
+
 		$scope.showAppDetail = function(application) {
 			var docs = application.docs || {install: "", uninstall: ""};
 			$scope.application = application;

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
----------------------------------------------------------------------
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 61c244d..8700f27 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
@@ -119,7 +119,13 @@
 
 		// TODO: metadata will be removed
 		Entity.queryMetadata = function (url) {
-			return Entity.query('metadata/' +  url);
+			var metaList = Entity.query('metadata/' +  url);
+			metaList._then(function (res) {
+				metaList.splice(0);
+				Array.prototype.push.apply(metaList, res.data);
+			});
+
+			return metaList;
 		};
 
 		Entity.deleteMetadata = function (url) {

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
index b4a1a42..e0bb77d 100644
--- a/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
@@ -62,7 +62,7 @@
 			$scope = $rootScope.$new(true);
 			$scope.name = name;
 			$scope.entity = _entity;
-			$scope.fieldList = fieldList;
+			$scope.fieldList = fieldList.concat();
 			$scope.checkFunc = checkFunc;
 			$scope.lock = false;
 			$scope.create = create;
@@ -112,6 +112,35 @@
 				});
 			};
 
+			$scope.newField = function () {
+				UI.fieldConfirm({
+					title: "New Field"
+				}, null, [{
+					field: "field",
+					name: "Field Name"
+				}])(function (entity, closeFunc, unlock) {
+					if(common.array.find(entity.field, $scope.fieldList, "field")) {
+						$.dialog({
+							title: "OPS",
+							content: "Field already exist!"
+						});
+
+						unlock();
+					} else {
+						$scope.fieldList.push({
+							field: entity.field,
+							_customize: true
+						});
+
+						closeFunc();
+					}
+				});
+			};
+
+			$scope.removeField = function (field) {
+				$scope.fieldList = common.array.remove(field, $scope.fieldList);
+			};
+
 			$scope.confirm = function() {
 				$scope.lock = true;
 				_deferred.notify({
@@ -155,6 +184,7 @@
 		 * @param {object} config						- Configuration object
 		 * @param {string} config.title						- Title of dialog box
 		 * @param {string=} config.size						- "large". Set dialog size
+		 * @param {boolean=} config.addable					- Set add customize field
 		 * @param {boolean=} config.confirm					- Display or not confirm button
 		 * @param {string=} config.confirmDesc				- Confirm button display description
 		 * @param {object} entity						- bind entity
@@ -225,30 +255,32 @@
 		'<div class="modal-dialog" ng-class="{\'modal-lg\': config.size === \'large\'}" role="document">' +
 		'<div class="modal-content">' +
 		'<div class="modal-header">' +
-		'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
-		'<span aria-hidden="true">&times;</span>' +
-		'</button>' +
-		'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
+			'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
+				'<span aria-hidden="true">&times;</span>' +
+			'</button>' +
+			'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
 		'</div>' +
 		'<div class="modal-body">' +
-		'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
-		'<label for="featureName">' +
-		'<span ng-if="!field.optional">*</span> ' +
-		'{{field.name || field.field}}' +
-		'</label>' +
-		'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
-		'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field] = entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
-		'<option ng-repeat="value in field.valueList">{{value}}</option>' +
-		'</select>' +
-		'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>' +
-		'</div>' +
+			'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
+				'<label for="featureName">' +
+					'<span ng-if="!field.optional && !field._customize">*</span> ' +
+					'<a ng-if="field._customize" class="fa fa-times" ng-click="removeField(field)"></a> ' +
+					'{{field.name || field.field}}' +
+				'</label>' +
+				'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
+				'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field] = entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
+				'<option ng-repeat="value in field.valueList">{{value}}</option>' +
+				'</select>' +
+				'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>' +
+			'</div>' +
+			'<a ng-if="config.addable" ng-click="newField()">+ New field</a>' +
 		'</div>' +
 		'<div class="modal-footer">' +
-		'<p class="pull-left text-danger">{{checkFunc(entity)}}</p>' +
-		'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Close</button>' +
-		'<button type="button" class="btn btn-primary confirmBtn" ng-click="confirm()" ng-disabled="checkFunc(entity) || emptyFieldList().length || lock" ng-if="config.confirm !== false">' +
-		'{{config.confirmDesc || (create ? "Create" : "Update")}}' +
-		'</button>' +
+			'<p class="pull-left text-danger">{{checkFunc(entity)}}</p>' +
+			'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Close</button>' +
+			'<button type="button" class="btn btn-primary confirmBtn" ng-click="confirm()" ng-disabled="checkFunc(entity) || emptyFieldList().length || lock" ng-if="config.confirm !== false">' +
+				'{{config.confirmDesc || (create ? "Create" : "Update")}}' +
+			'</button>' +
 		'</div>' +
 		'</div>' +
 		'</div>' +

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/4e5641c3/eagle-server/src/main/webapp/app/package.json
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/package.json b/eagle-server/src/main/webapp/app/package.json
index f5b43bb..0bf2968 100644
--- a/eagle-server/src/main/webapp/app/package.json
+++ b/eagle-server/src/main/webapp/app/package.json
@@ -23,7 +23,7 @@
     "bootstrap": "3.3.6",
     "d3": "3.5.16",
     "echarts": "^3.2.3",
-    "font-awesome": "4.5.0",
+    "font-awesome": "4.6.3",
     "jquery": "2.2.4",
     "jquery-slimscroll": "1.3.6",
     "jsdom": "^9.5.0",


Mime
View raw message