[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">×</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">×</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",