[EAGLE-577] UI policy management

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

Author: zombieJ <smith3...@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 <smith3...@gmail.com>
Authored: Mon Oct 10 17:26:07 2016 +0800
Committer: Hao Chen <h...@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",

Reply via email to