http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html b/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html new file mode 100644 index 0000000..da6b75b --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html @@ -0,0 +1,33 @@ + +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--> +<table class="table table-properties"> + <thead> + <tr> + <th>Key</th> + <th>Value</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="entry in jobmanager.config | orderBy: 'key'"> + <td>{{entry.key}}</td> + <td>{{entry.value}}</td> + </tr> + </tbody> +</table> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html b/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html new file mode 100644 index 0000000..02c2f47 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html @@ -0,0 +1,33 @@ + +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--> +<nav class="navbar navbar-default navbar-fixed-top navbar-main"> + <div id="fold-button" ng-click="showSidebar()" class="btn btn-default navbar-btn pull-left"><i class="fa fa-navicon"></i></div> + <div class="navbar-title">Job Manager</div> +</nav> +<nav class="navbar navbar-default navbar-fixed-top navbar-main-additional"> + <ul class="nav nav-tabs"> + <li ui-sref-active="active"><a ui-sref=".config">Configuration</a></li> + <li ui-sref-active="active"><a ui-sref=".log">Logs</a></li> + <li ui-sref-active="active"><a ui-sref=".stdout">Stdout</a></li> + </ul> +</nav> +<div id="content-inner" class="has-navbar-main-additional"> + <div ui-view="details"></div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html b/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html new file mode 100644 index 0000000..df6a817 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html @@ -0,0 +1,40 @@ + +<!-- +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. + + +--> +<table class="table table-properties"> + <thead> + <tr> + <th colspan="2"> + <div class="row"> + <div class="col-xs-10">Job Manager Output</div> + <div class="col-xs-1 text-right"><a ng-click="reloadData()" class="show-pointer"><i class="fa fa-refresh"></i></a></div> + <div class="col-xs-1 text-left"><a href="jobmanager/stdout"><i class="fa fa-download"></i></a></div> + </div> + </th> + </tr> + </thead> + <tbody> + <tr> + <td colspan="2"> + <pre>{{jobmanager.stdout}}</pre> + </td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html b/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html new file mode 100644 index 0000000..b76278d --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html @@ -0,0 +1,53 @@ + +<!-- +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. + +--> +<nav class="navbar navbar-default navbar-fixed-top navbar-main"> + <div id="fold-button" ng-click="showSidebar()" class="btn btn-default navbar-btn pull-left"><i class="fa fa-navicon"></i></div> + <div class="navbar-title">Completed Jobs</div> +</nav> +<div id="content-inner"> + <table class="table table-hover table-clickable"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Job Name</th> + <th>Job ID</th> + <th>Tasks</th> + <th>Status</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="job in jobs" ui-sref="single-job.plan.overview({ jobid: job.jid })"> + <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job.duration}} ms</td> + <td>{{job.name}}</td> + <td>{{job.jid}}</td> + <td class="label-group"> + <bs-label status="{{status}}" ng-repeat="(status, value) in job.tasks">{{value}}</bs-label> + </td> + <td> + <bs-label status="{{job.state}}">{{job.state}}</bs-label> + </td> + </tr> + </tbody> + </table> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html new file mode 100644 index 0000000..a7a5d9d --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html @@ -0,0 +1,57 @@ + +<!-- +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. + +--> +<table ng-if="job['execution-config']" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Execution configuration</th> + </tr> + </thead> + <tbody> + <tr> + <td>Execution mode</td> + <td>{{ job['execution-config']['execution-mode'] }}</td> + </tr> + <tr> + <td>Max. number of execution retries</td> + <td>{{ job['execution-config']['max-execution-retries'] === -1 ? 'deactivated' : job['execution-config']['max-execution-retries'] }}</td> + </tr> + <tr> + <td>Job parallelism</td> + <td>{{ job['execution-config']['job-parallelism'] === -1 ? 'auto' : job['execution-config']['job-parallelism'] }}</td> + </tr> + <tr> + <td>Object reuse mode</td> + <td>{{ job['execution-config']['object-reuse-mode'] }}</td> + </tr> + </tbody> +</table> +<table ng-if="job['execution-config']['user-config']" class="table table-properties"> + <thead> + <tr> + <th colspan="2">User configuration</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in job['execution-config']['user-config']"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html new file mode 100644 index 0000000..a5f6676 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html @@ -0,0 +1,38 @@ + +<!-- +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 ng-if="exceptions['root-exception']" class="panel panel-default panel-multi"> + <div class="panel-heading clearfix"> + <div class="panel-title">Root exception</div> + </div> + <div class="panel-body"> + <pre class="exception">{{ exceptions['root-exception'] }}</pre> + </div> +</div> +<div ng-repeat="exception in exceptions['all-exceptions']" class="panel panel-default panel-multi"> + <div class="panel-heading clearfix"> + <div class="panel-title">{{ exception.task }}</div> + </div> + <div class="panel-heading clearfix"> + <div class="panel-info thin last"><span>{{ exception.location }}</span></div> + </div> + <div class="panel-body"> + <pre class="exception">{{ exception.exception }}</pre> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.html new file mode 100644 index 0000000..9d3e171 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.html @@ -0,0 +1,48 @@ + +<!-- +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. + +--> +<nav ng-if="job" class="navbar navbar-default navbar-fixed-top navbar-main"> + <div id="fold-button" ng-click="showSidebar()" class="btn btn-default navbar-btn pull-left"><i class="fa fa-navicon"></i></div> + <div class="navbar-title"> + <indicator-primary status="{{job.state}}"></indicator-primary>{{ job.name }} + </div> + <div class="navbar-info first last hidden-xs hidden-sm">{{ job.jid }}</div> + <div class="navbar-info first last"> + <div class="label-group"> + <bs-label status="{{status}}" ng-repeat="(status, value) in job['status-counts']">{{value}}</bs-label> + </div> + </div> + <div class="navbar-info first last hidden-xs hidden-sm">{{ job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}<span ng-if="job['end-time'] > -1"> + - + {{ job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></div> + <div ng-if="job.duration > -1" class="navbar-info last first">{{job.duration}} ms</div> +</nav> +<nav ng-if="job" class="navbar navbar-default navbar-fixed-top navbar-main-additional"> + <ul class="nav nav-tabs"> + <li ui-sref-active="active"><a ui-sref=".plan.overview">Plan</a></li> + <li ui-sref-active="active"><a ui-sref=".statistics">Job Accumulators / Statistics</a></li> + <li ui-sref-active="active"><a ui-sref=".timeline">Timeline</a></li> + <li ui-sref-active="active"><a ui-sref=".exceptions">Exceptions</a></li> + <li ui-sref-active="active"><a ui-sref=".properties">Properties</a></li> + <li ui-sref-active="active"><a ui-sref=".config">Configuration</a></li> + </ul> +</nav> +<div id="content-inner" class="has-navbar-main-additional"> + <div ui-view="details"></div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html new file mode 100644 index 0000000..f2c4143 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html @@ -0,0 +1,31 @@ + +<!-- +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="canvas-wrapper"> + <div job-plan="job-plan" plan="plan" jobid="{{jobid}}" set-node="changeNode(nodeid)" class="main-canvas"></div> +</div> +<div ng-if="plan" class="panel panel-default panel-multi"> + <nav class="navbar navbar-default navbar-secondary-additional"> + <ul class="nav nav-tabs"> + <li ui-sref-active="active"><a ui-sref=".overview({nodeid: nodeid})">Overview</a></li> + <li ui-sref-active="active"><a ui-sref=".accumulators({nodeid: nodeid})">Accumulators</a></li> + </ul> + </nav> + <div ui-view="node-details" class="panel-body clean"></div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html new file mode 100644 index 0000000..8de3921 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html @@ -0,0 +1,40 @@ + +<!-- +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. + +--> +<table class="table table-body-hover table-clickable table-activable"> + <thead> + <tr> + <th>Name</th> + <th>Status</th> + </tr> + </thead> + <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="v.id == nodeid || changeNode(v.id)"> + <tr ng-if="v.type == 'regular'"> + <td>{{ v.name | humanizeText }}</td> + <td> + <bs-label status="{{v.status}}">{{v.status}}</bs-label> + </td> + </tr> + <tr ng-if="nodeid && v.id == nodeid"> + <td colspan="10"> + <div ng-include=" 'partials/jobs/job.plan.node.accumulators.html' "></div> + </td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html new file mode 100644 index 0000000..1706d3e --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html @@ -0,0 +1,60 @@ + +<!-- +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. + +--> +<table class="table table-body-hover table-clickable table-activable"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Name</th> + <th>Bytes read</th> + <th>Records read</th> + <th>Bytes written</th> + <th>Records written</th> + <th>Tasks</th> + <th>Status</th> + </tr> + </thead> + <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="changeNode(v.id)"> + <tr ng-if="v.type == 'regular'"> + <td><span ng-if="v['start-time'] > -1">{{ v['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td> + <td><span ng-if="v['end-time'] > -1">{{ v['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td> + <td><span ng-if="v.duration > -1">{{ v.duration }} ms</span></td> + <td class="td-long">{{ v.name | humanizeText }}</td> + <td>{{ v.metrics['read-bytes'] }}</td> + <td>{{ v.metrics['read-records'] }}</td> + <td>{{ v.metrics['write-bytes'] }}</td> + <td>{{ v.metrics['write-records'] }}</td> + <td> + <div class="label-group"> + <bs-label status="{{status}}" ng-repeat="(index, status) in stateList">{{v.tasks[status]}}</bs-label> + </div> + </td> + <td> + <bs-label status="{{v.status}}">{{v.status}}</bs-label> + </td> + </tr> + <tr ng-if="nodeid && v.id == nodeid"> + <td colspan="10"> + <div ng-include=" 'partials/jobs/job.plan.node.subtasks.html' "></div> + </td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html new file mode 100644 index 0000000..e7dcf2c --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html @@ -0,0 +1,68 @@ + +<!-- +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 ng-if="accumulators.length == 0"> + <p><i>No accumulators</i></p> +</div> +<div ng-if="accumulators && accumulators.length > 0"> + <table class="table table-hover table-clickable table-activable table-inner"> + <thead> + <tr> + <th>Name</th> + <th>Type</th> + <th>Value</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="accumulator in accumulators"> + <td width="30%">{{ accumulator.name }}</td> + <td width="30%">{{ accumulator.type }}</td> + <td width="30%">{{ accumulator.value }}</td> + </tr> + </tbody> + </table> + <div ng-if="!nodeUnfolded"><a ng-click="toggleFold()" class="btn btn-default"> + Show subtasks + <i class="fa fa-chevron-down"></i></a><a ng-click="deactivateNode(); $event.stopPropagation()" title="Fold" class="btn btn-default pull-right"><i class="fa fa-chevron-up"></i></a></div> + <div ng-if="nodeUnfolded && subtaskAccumulators && subtaskAccumulators.length > 0"><a ng-click="toggleFold()" class="btn btn-default"> + Hide subtasks + <i class="fa fa-chevron-up"></i></a> + <table class="table table-hover table-clickable table-activable table-inner"> + <thead> + <tr> + <th>Name</th> + <th>Type</th> + <th>Value</th> + </tr> + </thead> + <tbody ng-if="subtask['user-accumulators'] && subtask['user-accumulators'].length > 0" ng-repeat="subtask in subtaskAccumulators"> + <tr> + <td colwidth="3"> + <div class="small-label">({{ subtask.subtask }}) {{ subtask.host }}, attempt: {{ subtask.attempt + 1 }}</div> + </td> + </tr> + <tr ng-repeat="accumulator in subtask['user-accumulators']"> + <td width="30%">{{ accumulator.name }}</td> + <td width="30%">{{ accumulator.type }}</td> + <td width="30%">{{ accumulator.value }}</td> + </tr> + </tbody> + </table> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html new file mode 100644 index 0000000..40b16bc --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html @@ -0,0 +1,52 @@ + +<!-- +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. + + +--> +<table ng-if="subtasks" class="table table-hover table-clickable table-activable table-inner"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Bytes read</th> + <th>Records read</th> + <th>Bytes written</th> + <th>Records written</th> + <th>Attempt</th> + <th>Host</th> + <th>Status</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="subtask in subtasks"> + <td><span ng-if="subtask['start-time'] > -1">{{ subtask['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td> + <td><span ng-if="subtask['end-time'] > -1">{{ subtask['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td> + <td><span ng-if="subtask.duration > -1">{{ subtask.duration }} ms</span></td> + <td><span ng-if="subtask.metrics['read-bytes'] > -1">{{ subtask.metrics['read-bytes'] }}</span></td> + <td><span ng-if="subtask.metrics['read-records'] > -1">{{ subtask.metrics['read-records'] }}</span></td> + <td><span ng-if="subtask.metrics['write-bytes'] > -1">{{ subtask.metrics['write-bytes'] }}</span></td> + <td><span ng-if="subtask.metrics['write-records'] > -1">{{ subtask.metrics['write-records'] }}</span></td> + <td>{{ subtask.attempt + 1 }}</td> + <td>{{ subtask.host }}</td> + <td> + <bs-label status="{{subtask.status}}">{{subtask.status}}</bs-label> + </td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html new file mode 100644 index 0000000..907afd3 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html @@ -0,0 +1,140 @@ + +<!-- +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="canvas-wrapper"> + <div job-plan="job-plan" plan="plan" jobid="{{jobid}}" set-node="changeNode(nodeid)" class="main-canvas"></div> +</div> +<div ng-if="node" class="panel panel-default"> + <div class="panel-heading clearfix"> + <div class="panel-title">{{ node.description | humanizeText }}</div> + </div> + <div class="panel-body clean"> + <div class="row"> + <div class="col-sm-6 col-md-4"> + <table ng-if="node.optimizer_properties.global_properties" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Global Data Properties</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in node.optimizer_properties.global_properties"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> + </table> + <table ng-if="node.optimizer_properties.local_properties" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Local Data Properties</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in node.optimizer_properties.local_properties"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> + </table> + <div class="visible-xs visible-sm"> + <table class="table table-properties"> + <thead> + <tr> + <th colspan="2">Properties</th> + </tr> + </thead> + <tbody> + <tr> + <td>Operator</td> + <td table-property="table-property" value="node.operator_strategy"></td> + </tr> + <tr> + <td>Parallelism</td> + <td table-property="table-property" value="node.parallelism"></td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="hidden-sm col-md-4"> + <table class="table table-properties"> + <thead> + <tr> + <th colspan="2">Properties</th> + </tr> + </thead> + <tbody> + <tr> + <td>Operator</td> + <td table-property="table-property" value="node.operator_strategy"></td> + </tr> + <tr> + <td>Parallelism</td> + <td table-property="table-property" value="node.parallelism"></td> + </tr> + </tbody> + </table> + <table ng-if="node.optimizer_properties.estimates" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Size Estimates</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in node.optimizer_properties.estimates"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> + </table> + </div> + <div class="col-sm-6 col-md-4"> + <div class="visible-xs visible-sm"> + <table ng-if="node.optimizer_properties.estimates" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Size Estimates</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in node.optimizer_properties.estimates"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> + </table> + </div> + <table ng-if="node.optimizer_properties.costs" class="table table-properties"> + <thead> + <tr> + <th colspan="2">Cost Estimates</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="property in node.optimizer_properties.costs"> + <td>{{property.name}}</td> + <td table-property="table-property" value="property.value"></td> + </tr> + </tbody> + </table> + </div> + </div> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html new file mode 100644 index 0000000..951cc1c --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html @@ -0,0 +1,40 @@ + +<!-- +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. + +--> +<table class="table table-properties"> + <thead> + <tr> + <th colspan="2">Some statistics</th> + </tr> + </thead> + <tbody> + <tr> + <td>Operator</td> + <td>1</td> + </tr> + <tr> + <td>Parallelism</td> + <td>2</td> + </tr> + <tr> + <td>Subtasks-per-instance</td> + <td>3</td> + </tr> + </tbody> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html new file mode 100644 index 0000000..2f22576 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html @@ -0,0 +1,23 @@ + +<!-- +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="canvas-wrapper"> + <div timeline="timeline" vertices="vertices" jobid="jobid" class="timeline-canvas"></div> +</div> +<div ui-view="vertex"></div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html new file mode 100644 index 0000000..1a4bd06 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html @@ -0,0 +1,30 @@ + +<!-- +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 ng-if="vertex" class="panel panel-default panel-multi"> + <div class="panel-heading clearfix"> + <div class="panel-title">{{ vertex.groupvertex.groupvertexname | humanizeText }}</div> + </div> + <div class="panel-body"> + <div class="canvas-wrapper"> + <div vertex="vertex" data="vertex" class="timeline-canvas"></div> + </div> + <div id="timeline1"></div> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html b/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html new file mode 100644 index 0000000..e175d07 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html @@ -0,0 +1,53 @@ + +<!-- +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. + +--> +<nav class="navbar navbar-default navbar-fixed-top navbar-main"> + <div id="fold-button" ng-click="showSidebar()" class="btn btn-default navbar-btn pull-left"><i class="fa fa-navicon"></i></div> + <div class="navbar-title">Running Jobs</div> +</nav> +<div id="content-inner"> + <table class="table table-hover table-clickable"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Job Name</th> + <th>Job ID</th> + <th>Tasks</th> + <th>Status</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="job in jobs" ui-sref="single-job.plan.overview({ jobid: job.jid })"> + <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job.duration}} ms</td> + <td>{{job.name}}</td> + <td>{{job.jid}}</td> + <td class="label-group"> + <bs-label status="{{status}}" ng-repeat="(status, value) in job.tasks">{{value}}</bs-label> + </td> + <td> + <bs-label status="{{job.state}}">{{job.state}}</bs-label> + </td> + </tr> + </tbody> + </table> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/overview.html ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/main/resources/web/partials/overview.html b/flink-runtime-web/src/main/resources/web/partials/overview.html new file mode 100644 index 0000000..ec3c580 --- /dev/null +++ b/flink-runtime-web/src/main/resources/web/partials/overview.html @@ -0,0 +1,147 @@ + +<!-- +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. + +--> +<nav class="navbar navbar-default navbar-fixed-top navbar-main"> + <div id="fold-button" ng-click="showSidebar()" class="btn btn-default navbar-btn pull-left"><i class="fa fa-navicon"></i></div> + <div class="navbar-title">Overview</div> +</nav> +<div id="content-inner"> + <div class="row"> + <div class="col-md-6"> + <div class="panel panel-default panel-dashboard"> + <div class="panel-heading"> + <div class="row"> + <div class="col-xs-3"><i class="fa fa-tasks fa-3x"></i></div> + <div class="col-xs-9 text-right"> + <div class="huge">{{overview.taskmanagers}}</div> + <div>Task Managers</div> + </div> + </div> + </div> + <div class="panel-heading"> + <div class="row"> + <div class="col-xs-3"><i class="fa fa-folder fa-3x"></i></div> + <div class="col-xs-9 text-right"> + <div class="huge">{{overview["slots-total"]}}</div> + <div>Task Slots</div> + </div> + </div> + </div> + <div class="panel-heading"> + <div class="row"> + <div class="col-xs-3"><i class="fa fa-folder-o fa-3x"></i></div> + <div class="col-xs-9 text-right"> + <div class="huge">{{overview["slots-available"]}}</div> + <div>Available Task Slots</div> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-6"> + <div class="panel panel-default panel-lg"> + <div class="panel-heading">Total Jobs</div> + <div class="list-group"> + <div class="list-group-item"> + <div class="badge badge-primary">{{overview["jobs-running"]}}</div>Running + </div> + <div class="list-group-item"> + <div class="badge badge-success">{{overview["jobs-finished"]}}</div>Finished + </div> + <div class="list-group-item"> + <div class="badge badge-info">{{overview["jobs-cancelled"]}}</div>Canceled + </div> + <div class="list-group-item"> + <div class="badge badge-danger">{{overview["jobs-failed"]}}</div>Failed + </div> + </div> + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Running Jobs</h3> + </div> + <div class="panel-body"> + <table class="table table-hover table-clickable"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Job Name</th> + <th>Job ID</th> + <th>Tasks</th> + <th>Status</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="job in runningJobs" ui-sref="single-job.plan.overview({ jobid: job.jid })"> + <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job.duration}} ms</td> + <td>{{job.name}}</td> + <td>{{job.jid}}</td> + <td class="label-group"> + <bs-label status="{{status}}" ng-repeat="(status, value) in job.tasks">{{value}}</bs-label> + </td> + <td> + <bs-label status="{{job.state}}">{{job.state}}</bs-label> + </td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Completed Jobs</h3> + </div> + <div class="panel-body"> + <table class="table table-hover table-clickable"> + <thead> + <tr> + <th>Start Time</th> + <th>End Time</th> + <th>Duration</th> + <th>Job Name</th> + <th>Job ID</th> + <th>Tasks</th> + <th>Status</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="job in finishedJobs" ui-sref="single-job.plan.overview({ jobid: job.jid })"> + <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td> + <td>{{job.duration}} ms</td> + <td>{{job.name}}</td> + <td>{{job.jid}}</td> + <td class="label-group"> + <bs-label status="{{status}}" ng-repeat="(status, value) in job.tasks">{{value}}</bs-label> + </td> + <td> + <bs-label status="{{job.state}}">{{job.state}}</bs-label> + </td> + </tr> + </tbody> + </table> + </div> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java new file mode 100644 index 0000000..26f66b0 --- /dev/null +++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java @@ -0,0 +1,329 @@ +/* + * 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. + */ + +package org.apache.flink.runtime.webmonitor; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.curator.test.TestingServer; +import org.apache.flink.configuration.ConfigConstants; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.StreamingMode; +import org.apache.flink.runtime.akka.AkkaUtils; +import org.apache.flink.runtime.jobmanager.JobManager; +import org.apache.flink.runtime.leaderelection.TestingListener; +import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService; +import org.apache.flink.runtime.testingUtils.TestingCluster; +import org.apache.flink.runtime.util.ZooKeeperUtils; +import org.apache.flink.runtime.webmonitor.files.MimeTypes; +import org.apache.flink.runtime.webmonitor.testutils.HttpTestClient; +import org.junit.Test; +import org.powermock.reflect.Whitebox; +import scala.Some; +import scala.Tuple2; +import scala.concurrent.duration.Deadline; +import scala.concurrent.duration.FiniteDuration; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class WebRuntimeMonitorITCase { + + private final static FiniteDuration TestTimeout = new FiniteDuration(2, TimeUnit.MINUTES); + + private final String MAIN_RESOURCES_PATH = getClass().getResource("/../classes/web").getPath(); + + /** + * Tests operation of the monitor in standalone operation. + */ + @Test + public void testStandaloneWebRuntimeMonitor() throws Exception { + final Deadline deadline = TestTimeout.fromNow(); + + TestingCluster flink = null; + WebRuntimeMonitor webMonitor = null; + + try { + // Flink w/o a web monitor + flink = new TestingCluster(new Configuration()); + flink.start(true); + + ActorSystem jmActorSystem = flink.jobManagerActorSystems().get().head(); + ActorRef jmActor = flink.jobManagerActors().get().head(); + + Configuration monitorConfig = new Configuration(); + monitorConfig.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, MAIN_RESOURCES_PATH); + monitorConfig.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, true); + + // Needs to match the leader address from the leader retrieval service + String jobManagerAddress = AkkaUtils.getAkkaURL(jmActorSystem, jmActor); + + webMonitor = new WebRuntimeMonitor(monitorConfig, flink.createLeaderRetrievalService(), + jmActorSystem); + + webMonitor.start(jobManagerAddress); + + try (HttpTestClient client = new HttpTestClient("localhost", webMonitor.getServerPort())) { + String expected = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html")) + .useDelimiter("\\A").next(); + + // Request the file from the web server + client.sendGetRequest("index.html", deadline.timeLeft()); + + HttpTestClient.SimpleHttpResponse response = client.getNextResponse(deadline.timeLeft()); + + assertEquals(HttpResponseStatus.OK, response.getStatus()); + assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html")); + assertEquals(expected, response.getContent()); + + // Simple overview request + client.sendGetRequest("/overview", deadline.timeLeft()); + + response = client.getNextResponse(deadline.timeLeft()); + assertEquals(HttpResponseStatus.OK, response.getStatus()); + assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("json")); + assertTrue(response.getContent().contains("\"taskmanagers\":1")); + } + } + finally { + if (flink != null) { + flink.shutdown(); + } + + if (webMonitor != null) { + webMonitor.stop(); + } + } + } + + /** + * Tests that the monitor associated with the following job manager redirects to the leader. + */ + @Test + public void testRedirectToLeader() throws Exception { + final Deadline deadline = TestTimeout.fromNow(); + + ActorSystem[] jobManagerSystem = new ActorSystem[2]; + WebRuntimeMonitor[] webMonitor = new WebRuntimeMonitor[2]; + List<LeaderRetrievalService> leaderRetrievalServices = new ArrayList<>(); + + try (TestingServer zooKeeper = new TestingServer()) { + final Configuration config = new Configuration(); + config.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, MAIN_RESOURCES_PATH); + config.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, true); + config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0); + config.setString(ConfigConstants.RECOVERY_MODE, "ZOOKEEPER"); + config.setString(ConfigConstants.ZOOKEEPER_QUORUM_KEY, zooKeeper.getConnectString()); + + for (int i = 0; i < jobManagerSystem.length; i++) { + jobManagerSystem[i] = AkkaUtils.createActorSystem(new Configuration(), + new Some<>(new Tuple2<String, Object>("localhost", 0))); + } + + for (int i = 0; i < webMonitor.length; i++) { + LeaderRetrievalService lrs = ZooKeeperUtils.createLeaderRetrievalService(config); + leaderRetrievalServices.add(lrs); + webMonitor[i] = new WebRuntimeMonitor(config, lrs, jobManagerSystem[i]); + } + + ActorRef[] jobManager = new ActorRef[2]; + String[] jobManagerAddress = new String[2]; + for (int i = 0; i < jobManager.length; i++) { + Configuration jmConfig = config.clone(); + jmConfig.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, + webMonitor[i].getServerPort()); + + jobManager[i] = JobManager.startJobManagerActors( + jmConfig, jobManagerSystem[i], StreamingMode.STREAMING)._1(); + + jobManagerAddress[i] = AkkaUtils.getAkkaURL(jobManagerSystem[i], jobManager[i]); + webMonitor[i].start(jobManagerAddress[i]); + } + + LeaderRetrievalService lrs = ZooKeeperUtils.createLeaderRetrievalService(config); + leaderRetrievalServices.add(lrs); + TestingListener leaderListener = new TestingListener(); + lrs.start(leaderListener); + + leaderListener.waitForNewLeader(deadline.timeLeft().toMillis()); + + String leaderAddress = leaderListener.getAddress(); + + int leaderIndex = leaderAddress.equals(jobManagerAddress[0]) ? 0 : 1; + int followerIndex = (leaderIndex + 1) % 2; + + ActorSystem leadingSystem = jobManagerSystem[leaderIndex]; + ActorSystem followerSystem = jobManagerSystem[followerIndex]; + + WebMonitor leadingWebMonitor = webMonitor[leaderIndex]; + WebMonitor followerWebMonitor = webMonitor[followerIndex]; + + // For test stability reason we have to wait until we are sure that both leader + // listeners have been notified. + JobManagerRetriever leadingRetriever = Whitebox + .getInternalState(leadingWebMonitor, "retriever"); + + JobManagerRetriever followerRetriever = Whitebox + .getInternalState(followerWebMonitor, "retriever"); + + // Wait for the initial notifications + waitForLeaderNotification(leadingSystem, jobManager[leaderIndex], leadingRetriever, deadline); + waitForLeaderNotification(leadingSystem, jobManager[leaderIndex], followerRetriever, deadline); + + try ( + HttpTestClient leaderClient = new HttpTestClient( + "localhost", leadingWebMonitor.getServerPort()); + + HttpTestClient followingClient = new HttpTestClient( + "localhost", followerWebMonitor.getServerPort())) { + + String expected = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html")) + .useDelimiter("\\A").next(); + + // Request the file from the leaading web server + leaderClient.sendGetRequest("index.html", deadline.timeLeft()); + + HttpTestClient.SimpleHttpResponse response = leaderClient.getNextResponse(deadline.timeLeft()); + assertEquals(HttpResponseStatus.OK, response.getStatus()); + assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html")); + assertEquals(expected, response.getContent()); + + // Request the file from the following web server + followingClient.sendGetRequest("index.html", deadline.timeLeft()); + response = followingClient.getNextResponse(deadline.timeLeft()); + assertEquals(HttpResponseStatus.TEMPORARY_REDIRECT, response.getStatus()); + assertTrue(response.getLocation().contains("" + leadingWebMonitor.getServerPort())); + + // Kill the leader + leadingSystem.shutdown(); + + // Wait for the notification of the follower + waitForLeaderNotification(followerSystem, jobManager[followerIndex], followerRetriever, deadline); + + // Same request to the new leader + followingClient.sendGetRequest("index.html", deadline.timeLeft()); + + response = followingClient.getNextResponse(deadline.timeLeft()); + assertEquals(HttpResponseStatus.OK, response.getStatus()); + assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html")); + assertEquals(expected, response.getContent()); + + // Simple overview request + followingClient.sendGetRequest("/overview", deadline.timeLeft()); + + response = followingClient.getNextResponse(deadline.timeLeft()); + assertEquals(HttpResponseStatus.OK, response.getStatus()); + assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("json")); + assertTrue(response.getContent().contains("\"taskmanagers\":1") || + response.getContent().contains("\"taskmanagers\":0")); + } + } + finally { + for (ActorSystem system : jobManagerSystem) { + if (system != null) { + system.shutdown(); + } + } + + for (WebMonitor monitor : webMonitor) { + monitor.stop(); + } + + for (LeaderRetrievalService lrs : leaderRetrievalServices) { + lrs.stop(); + } + } + } + + @Test + public void testLeaderNotAvailable() throws Exception { + final Deadline deadline = TestTimeout.fromNow(); + + ActorSystem actorSystem = null; + WebRuntimeMonitor webRuntimeMonitor = null; + + try (TestingServer zooKeeper = new TestingServer()) { + + final Configuration config = new Configuration(); + config.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, MAIN_RESOURCES_PATH); + config.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, true); + config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0); + config.setString(ConfigConstants.RECOVERY_MODE, "ZOOKEEPER"); + config.setString(ConfigConstants.ZOOKEEPER_QUORUM_KEY, zooKeeper.getConnectString()); + + actorSystem = AkkaUtils.createDefaultActorSystem(); + + LeaderRetrievalService leaderRetrievalService = mock(LeaderRetrievalService.class); + webRuntimeMonitor = new WebRuntimeMonitor( + config, leaderRetrievalService, actorSystem); + + webRuntimeMonitor.start("akka://schmakka"); + + try (HttpTestClient client = new HttpTestClient( + "localhost", webRuntimeMonitor.getServerPort())) { + + client.sendGetRequest("index.html", deadline.timeLeft()); + + HttpTestClient.SimpleHttpResponse response = client.getNextResponse(); + + assertEquals(HttpResponseStatus.SERVICE_UNAVAILABLE, response.getStatus()); + assertEquals(MimeTypes.getMimeTypeForExtension("txt"), response.getType()); + assertTrue(response.getContent().contains("refresh")); + } + } + finally { + if (actorSystem != null) { + actorSystem.shutdown(); + } + + if (webRuntimeMonitor != null) { + webRuntimeMonitor.stop(); + } + } + } + + // ------------------------------------------------------------------------ + + private void waitForLeaderNotification( + ActorSystem system, + ActorRef expectedLeader, + JobManagerRetriever retriever, + Deadline deadline) throws Exception { + + String expectedJobManagerUrl = AkkaUtils.getAkkaURL(system, expectedLeader); + + while (deadline.hasTimeLeft()) { + ActorRef leaderRef = retriever.awaitJobManagerGatewayAndWebPort()._1().actor(); + + if (AkkaUtils.getAkkaURL(system, leaderRef).equals(expectedJobManagerUrl)) { + return; + } + else { + Thread.sleep(100); + } + } + } +} http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java ---------------------------------------------------------------------- diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java new file mode 100644 index 0000000..d7d4457 --- /dev/null +++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java @@ -0,0 +1,309 @@ +/* + * 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. + */ + +package org.apache.flink.runtime.webmonitor.testutils; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpContentDecompressor; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.CharsetUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.concurrent.duration.FiniteDuration; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A simple HTTP client. + * + * <pre> + * HttpTestClient client = new HttpTestClient("localhost", 8081); + * client.sendGetRequest("/overview", timeout); + * SimpleHttpResponse response = client.getNextResponse(timeout); + * + * assertEquals(200, response.getStatus().code()); // OK + * assertEquals("application/json", response.getType()); + * assertTrue(response.getContent().contains("\"jobs-running\":0")); + * </pre> + * + * This code is based on Netty's HttpSnoopClient. + * + * @see <a href="https://github.com/netty/netty/blob/master/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java">HttpSnoopClient</a> + */ +public class HttpTestClient implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(HttpTestClient.class); + + /** Target host */ + private final String host; + + /** Target port */ + private final int port; + + /** Netty's thread group for the client */ + private final EventLoopGroup group; + + /** Client bootstrap */ + private final Bootstrap bootstrap; + + /** Responses received by the client */ + private final BlockingQueue<SimpleHttpResponse> responses = new LinkedBlockingQueue<>(); + + /** + * Creates a client instance for the server at the target host and port. + * + * @param host Host of the HTTP server + * @param port Port of the HTTP server + */ + public HttpTestClient(String host, int port) { + this.host = host; + this.port = port; + + this.group = new NioEventLoopGroup(); + + this.bootstrap = new Bootstrap(); + this.bootstrap.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer<SocketChannel>() { + + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast(new HttpClientCodec()); + p.addLast(new HttpContentDecompressor()); + p.addLast(new ClientHandler(responses)); + } + }); + } + + /** + * Sends a request to to the server. + * + * <pre> + * HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/overview"); + * request.headers().set(HttpHeaders.Names.HOST, host); + * request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); + * + * sendRequest(request); + * </pre> + * + * @param request The {@link HttpRequest} to send to the server + */ + public void sendRequest(HttpRequest request, FiniteDuration timeout) throws InterruptedException, TimeoutException { + LOG.debug("Writing {}.", request); + + // Make the connection attempt. + ChannelFuture connect = bootstrap.connect(host, port); + + Channel channel; + if (connect.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + channel = connect.channel(); + } + else { + throw new TimeoutException("Connection failed"); + } + + channel.writeAndFlush(request); + } + + /** + * Sends a simple GET request to the given path. You only specify the $path part of + * http://$host:$host/$path. + * + * @param path The $path to GET (http://$host:$host/$path) + */ + public void sendGetRequest(String path, FiniteDuration timeout) throws TimeoutException, InterruptedException { + if (!path.startsWith("/")) { + path = "/" + path; + } + + HttpRequest getRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.GET, path); + getRequest.headers().set(HttpHeaders.Names.HOST, host); + getRequest.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); + + sendRequest(getRequest, timeout); + } + + /** + * Returns the next available HTTP response. A call to this method blocks until a response + * becomes available. + * + * @return The next available {@link SimpleHttpResponse} + */ + public SimpleHttpResponse getNextResponse() throws InterruptedException { + return responses.take(); + } + + /** + * Returns the next available HTTP response . A call to this method blocks until a response + * becomes available or throws an Exception if the timeout fires. + * + * @param timeout Timeout in milliseconds for the next response to become available + * @return The next available {@link SimpleHttpResponse} + */ + public SimpleHttpResponse getNextResponse(FiniteDuration timeout) throws InterruptedException, + TimeoutException { + + SimpleHttpResponse response = responses.poll(timeout.toMillis(), TimeUnit.MILLISECONDS); + + if (response == null) { + throw new TimeoutException("No response within timeout of " + timeout + " ms"); + } + else { + return response; + } + } + + /** + * Closes the client. + */ + @Override + public void close() throws InterruptedException { + if (group != null) { + group.shutdownGracefully(); + } + + LOG.debug("Closed"); + } + + /** + * A simple HTTP response. + */ + public static class SimpleHttpResponse { + + private final HttpResponseStatus status; + + private final String type; + + private final String content; + + private final String location; + + public SimpleHttpResponse(HttpResponseStatus status, String type, String content, String location) { + this.status = status; + this.type = type; + this.content = content; + this.location = location; + } + + public HttpResponseStatus getStatus() { + return status; + } + + public String getType() { + return type; + } + + public final String getLocation() { + return location; + } + + public String getContent() { + return content; + } + + @Override + public String toString() { + return "HttpResponse(status=" + status + ", type='" + type + "'" + ", content='" + + content + "')"; + } + } + + /** + * The response handler. Responses from the server are handled here. + */ + @ChannelHandler.Sharable + private static class ClientHandler extends SimpleChannelInboundHandler<HttpObject> { + + private final BlockingQueue<SimpleHttpResponse> responses; + + private HttpResponseStatus currentStatus; + + private String currentType; + + private String currentLocation; + + private String currentContent = ""; + + public ClientHandler(BlockingQueue<SimpleHttpResponse> responses) { + this.responses = responses; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { + LOG.debug("Received {}", msg); + + if (msg instanceof HttpResponse) { + HttpResponse response = (HttpResponse) msg; + + currentStatus = response.getStatus(); + currentType = response.headers().get(HttpHeaders.Names.CONTENT_TYPE); + currentLocation = response.headers().get(HttpHeaders.Names.LOCATION); + + if (HttpHeaders.isTransferEncodingChunked(response)) { + LOG.debug("Content is chunked"); + } + } + + if (msg instanceof HttpContent) { + HttpContent content = (HttpContent) msg; + + // Add the content + currentContent += content.content().toString(CharsetUtil.UTF_8); + + // Finished with this + if (content instanceof LastHttpContent) { + responses.add(new SimpleHttpResponse(currentStatus, currentType, + currentContent, currentLocation)); + + currentStatus = null; + currentType = null; + currentLocation = null; + currentContent = ""; + + ctx.close(); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/web-dashboard/gulpfile.js ---------------------------------------------------------------------- diff --git a/flink-runtime-web/web-dashboard/gulpfile.js b/flink-runtime-web/web-dashboard/gulpfile.js index 737519e..2abfcce 100644 --- a/flink-runtime-web/web-dashboard/gulpfile.js +++ b/flink-runtime-web/web-dashboard/gulpfile.js @@ -43,7 +43,7 @@ var path = require('path'); var environment = 'development'; var paths = { src: './app/', - dest: './web/', + dest: '../src/main/resources/web/', vendor: './bower_components/', vendorLocal: './vendor-local/', assets: './assets/', @@ -168,7 +168,7 @@ gulp.task('watch', function () { }); gulp.task('serve', serve({ - root: 'web', + root: '../src/main/resources/web/', port: 3001 })); http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/web-dashboard/web/css/index.css ---------------------------------------------------------------------- diff --git a/flink-runtime-web/web-dashboard/web/css/index.css b/flink-runtime-web/web-dashboard/web/css/index.css deleted file mode 100644 index 43f7d98..0000000 --- a/flink-runtime-web/web-dashboard/web/css/index.css +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#sidebar { - overflow: hidden; - position: fixed; - left: -250px; - top: 0; - bottom: 0; - height: 100%; - width: 250px; - background: #151515; - -webkit-transition: 400ms; - -moz-transition: 400ms; - -o-transition: 400ms; - -ms-transition: 400ms; - transition: 400ms; - -webkit-box-shadow: inset -10px 0px 10px rgba(0,0,0,0.2); - box-shadow: inset -10px 0px 10px rgba(0,0,0,0.2); -} -#sidebar.sidebar-visible { - left: 0; -} -#sidebar .logo { - width: auto; - height: 22px; -} -#sidebar .logo img { - display: inline-block; -} -#sidebar .navbar-static-top { - overflow: hidden; - height: 51px; -} -#sidebar .navbar-static-top .navbar-header { - width: 100%; -} -#sidebar .navbar-brand.navbar-brand-text { - font-size: 14px; - font-weight: bold; - color: #fff; - padding-left: 0; -} -#sidebar .nav > li > a { - color: #aaa; - margin-bottom: 1px; -} -#sidebar .nav > li > a:hover, -#sidebar .nav > li > a:focus { - background-color: rgba(40,40,40,0.5); -} -#sidebar .nav > li > a.active { - background-color: rgba(100,100,100,0.5); -} -#content { - background-color: #fff; - overflow: hidden; - margin-left: 0; - padding-top: 70px; - -webkit-transition: 400ms; - -moz-transition: 400ms; - -o-transition: 400ms; - -ms-transition: 400ms; - transition: 400ms; -} -#content .navbar-main, -#content .navbar-main-additional { - -webkit-transition: 400ms; - -moz-transition: 400ms; - -o-transition: 400ms; - -ms-transition: 400ms; - transition: 400ms; -} -#content .navbar-main-additional { - margin-top: 51px; - border-bottom: none; - padding: 0 20px; -} -#content .navbar-main-additional .nav-tabs { - margin: 0 -20px; - padding: 0 20px; -} -#content .navbar-secondary-additional { - border: none; - padding: 0 20px; - margin-bottom: 0; -} -#content .navbar-secondary-additional .nav-tabs { - margin: 0 -20px; -} -#content.sidebar-visible { - margin-left: 250px; -} -#content.sidebar-visible .navbar-main, -#content.sidebar-visible .navbar-main-additional { - left: 250px; -} -#content #fold-button { - display: inline-block; - margin-left: 20px; -} -#content #content-inner { - padding: 0px 20px 20px 20px; -} -#content #content-inner.has-navbar-main-additional { - padding-top: 42px; -} -.page-header { - margin: 0 0 20px 0; -} -.nav > li > a, -.nav > li > a:hover, -.nav > li > a:focus { - color: #aaa; - background-color: transparent; - border-bottom: 2px solid transparent; -} -.nav > li.active > a, -.nav > li.active > a:hover, -.nav > li.active > a:focus { - color: #000; - border-bottom: 2px solid #000; -} -.nav.nav-tabs { - margin-bottom: 20px; -} -.table .table { - background-color: transparent; -} -.table th { - font-weight: normal; - color: #999; -} -.table td.td-long { - width: 20%; - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; -} -.table.table-clickable tr { - cursor: pointer; -} -.table.table-inner { - background-color: transparent; -} -.table.table-properties { - table-layout: fixed; - white-space: nowrap; -} -.table.table-properties td { - width: 50%; - white-space: nowrap; - overflow: hidden; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; -} -.table.table-body-hover > tbody { - border-top: none; - border-left: 2px solid transparent; -} -.table.table-body-hover > tbody.active { - border-left: 2px solid #000; -} -.table.table-body-hover > tbody:hover td:not(.tab-column), -.table.table-body-hover > tbody.active td:not(.tab-column) { - background-color: #f0f0f0; -} -.table.table-body-hover > tbody:hover td.tab-column li.active, -.table.table-body-hover > tbody.active td.tab-column li.active { - background-color: #f0f0f0; -} -.table.table-activable th.tab-column, -.table.table-activable td.tab-column { - border-top: none; - width: 47px; -} -.table.table-activable td.tab-column { - border-right: 1px solid #ddd; -} -.table.table-activable td { - position: relative; -} -.table .small-label { - text-transform: uppercase; - font-size: 13px; - color: #999; -} -.panel.panel-dashboard .huge { - font-size: 28px; -} -.panel.panel-lg { - font-size: 16px; -} -.panel.panel-lg .badge { - font-size: 14px; -} -.navbar-secondary { - overflow: auto; -} -.navbar-main .navbar-title, -.navbar-secondary .navbar-title, -.navbar-main-additional .navbar-title, -.panel.panel-multi .navbar-title, -.navbar-secondary-additional .navbar-title, -.navbar-main .panel-title, -.navbar-secondary .panel-title, -.navbar-main-additional .panel-title, -.panel.panel-multi .panel-title, -.navbar-secondary-additional .panel-title { - float: left; - font-size: 18px; - padding: 12px 20px 13px 10px; - color: #333; - display: inline-block; -} -.navbar-main .navbar-info, -.navbar-secondary .navbar-info, -.navbar-main-additional .navbar-info, -.panel.panel-multi .navbar-info, -.navbar-secondary-additional .navbar-info, -.navbar-main .panel-info, -.navbar-secondary .panel-info, -.navbar-main-additional .panel-info, -.panel.panel-multi .panel-info, -.navbar-secondary-additional .panel-info { - float: left; - font-size: 14px; - padding: 15px 15px 15px 15px; - color: #999; - display: inline-block; - border-right: 1px solid #e7e7e7; - overflow: hidden; -} -.navbar-main .navbar-info .overflow, -.navbar-secondary .navbar-info .overflow, -.navbar-main-additional .navbar-info .overflow, -.panel.panel-multi .navbar-info .overflow, -.navbar-secondary-additional .navbar-info .overflow, -.navbar-main .panel-info .overflow, -.navbar-secondary .panel-info .overflow, -.navbar-main-additional .panel-info .overflow, -.panel.panel-multi .panel-info .overflow, -.navbar-secondary-additional .panel-info .overflow { - position: absolute; - display: block; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; - overflow: hidden; - height: 22px; - line-height: 22px; - vertical-align: middle; -} -.navbar-main .navbar-info.first, -.navbar-secondary .navbar-info.first, -.navbar-main-additional .navbar-info.first, -.panel.panel-multi .navbar-info.first, -.navbar-secondary-additional .navbar-info.first, -.navbar-main .panel-info.first, -.navbar-secondary .panel-info.first, -.navbar-main-additional .panel-info.first, -.panel.panel-multi .panel-info.first, -.navbar-secondary-additional .panel-info.first { - border-left: 1px solid #e7e7e7; -} -.navbar-main .navbar-info.last, -.navbar-secondary .navbar-info.last, -.navbar-main-additional .navbar-info.last, -.panel.panel-multi .navbar-info.last, -.navbar-secondary-additional .navbar-info.last, -.navbar-main .panel-info.last, -.navbar-secondary .panel-info.last, -.navbar-main-additional .panel-info.last, -.panel.panel-multi .panel-info.last, -.navbar-secondary-additional .panel-info.last { - border-right: none; -} -.panel.panel-multi .panel-heading { - padding: 0; -} -.panel.panel-multi .panel-heading .panel-info.thin { - padding: 8px 10px; -} -.panel.panel-multi .panel-body { - padding: 10px; - background-color: #fdfdfd; - color: #999; - font-size: 13px; -} -.panel.panel-multi .panel-body.clean { - color: inherit; - font-size: inherit; -} -.navbar-main-additional, -.navbar-secondary-additional { - min-height: 40px; - background-color: #fdfdfd; -} -.navbar-main-additional .navbar-info, -.navbar-secondary-additional .navbar-info { - font-size: 13px; - padding: 10px 15px 10px 15px; -} -.nav-top-affix.affix { - width: 100%; - top: 50px; - margin-left: -20px; - padding-left: 20px; - margin-right: -20px; - padding-right: 20px; - background-color: #fff; - z-index: 1; -} -.badge-default[href]:hover, -.badge-default[href]:focus { - background-color: #808080; -} -.badge-primary { - background-color: #428bca; -} -.badge-primary[href]:hover, -.badge-primary[href]:focus { - background-color: #3071a9; -} -.badge-success { - background-color: #5cb85c; -} -.badge-success[href]:hover, -.badge-success[href]:focus { - background-color: #449d44; -} -.badge-info { - background-color: #5bc0de; -} -.badge-info[href]:hover, -.badge-info[href]:focus { - background-color: #31b0d5; -} -.badge-warning { - background-color: #f0ad4e; -} -.badge-warning[href]:hover, -.badge-warning[href]:focus { - background-color: #ec971f; -} -.badge-danger { - background-color: #d9534f; -} -.badge-danger[href]:hover, -.badge-danger[href]:focus { - background-color: #c9302c; -} -.indicator { - display: inline-block; - margin-right: 15px; -} -.indicator.indicator-primary { - color: #428bca; -} -.indicator.indicator-success { - color: #5cb85c; -} -.indicator.indicator-info { - color: #5bc0de; -} -.indicator.indicator-warning { - color: #f0ad4e; -} -.indicator.indicator-danger { - color: #d9534f; -} -pre.exception { - border: none; - background-color: transparent; - padding: 0; - margin: 0; -} -.nav-tabs.tabs-vertical { - position: absolute; - left: 0; - top: 0; - border-bottom: none; - z-index: 100; -} -.nav-tabs.tabs-vertical li { - float: none; - margin-bottom: 0; - margin-right: -1px; -} -.nav-tabs.tabs-vertical li > a { - margin-right: 0; - -webkit-border-radius: 0; - border-radius: 0; - border-bottom: none; - border-left: 2px solid transparent; -} -.nav-tabs.tabs-vertical li > a:hover, -.nav-tabs.tabs-vertical li > a:focus { - border-bottom: none; - border-left: 2px solid #000; -} -.nav-tabs.tabs-vertical li.active > a { - border-bottom: none; - border-left: 2px solid #000; -} -.navbar-main .navbar-title, -.navbar-secondary .navbar-title, -.navbar-main-additional .navbar-title, -.navbar-secondary-additional .navbar-title { - padding: 12px 20px 13px 20px; -} -livechart { - width: 30%; - height: 30%; - text-align: center; -} -.canvas-wrapper { - border: 1px solid #ddd; - position: relative; - margin-bottom: 20px; -} -.canvas-wrapper .main-canvas { - height: 400px; - overflow: hidden; -} -.canvas-wrapper .main-canvas .zoom-buttons { - position: absolute; - top: 10px; - right: 10px; -} -.label-group .label { - display: inline-block; - width: 2em; - padding-left: 0.1em; - padding-right: 0.1em; - margin: 0; - border-right: 1px solid #fff; - -webkit-border-radius: 0; - border-radius: 0; -} -.label-group .label.label-black { - background-color: #000; -} -svg.graph { - overflow: hidden; -} -svg.graph g.type-TK > rect { - fill: #00ffd0; -} -svg.graph text { - font-weight: 300; - font-size: 14px; -} -svg.graph .node { - cursor: pointer; -} -svg.graph .node > rect { - stroke: #999; - stroke-width: 5px; - fill: #fff; - margin: 0; - padding: 0; -} -svg.graph .node[active] > rect { - fill: #eee; -} -svg.graph .node.node-mirror > rect { - stroke: #a8a8a8; -} -svg.graph .node.node-iteration > rect { - stroke: #cd3333; -} -svg.graph .node.node-source > rect { - stroke: #4ce199; -} -svg.graph .node.node-sink > rect { - stroke: #e6ec8b; -} -svg.graph .node.node-normal > rect { - stroke: #3fb6d8; -} -svg.graph .node h4 { - color: #000; -} -svg.graph .node h5 { - color: #999; -} -svg.graph .edgeLabel rect { - fill: #fff; -} -svg.graph .edgePath path { - stroke: #333; - stroke-width: 2px; - fill: #333; -} -svg.graph .label { - color: #777; - margin: 0; -} -svg.graph .edge-label { - font-size: 14px; -} -svg.graph .node-label { - display: block; - margin: 0; - text-decoration: none; -} -.timeline { - overflow: hidden; -} -.timeline-canvas { - overflow: hidden; - padding: 10px; -} -.timeline-canvas .bar-container { - overflow: hidden; -} -.timeline-canvas .timeline-insidelabel, -.timeline-canvas .timeline-series { - cursor: pointer; -} -.timeline-canvas.secondary .timeline-insidelabel, -.timeline-canvas.secondary .timeline-series { - cursor: auto; -} -.qtip-timeline-bar { - font-size: 14px; - line-height: 1.4; -} -@media (min-width: 1024px) and (max-width: 1279px) { - #sidebar { - left: 0; - width: 160px; - } - #sidebar .navbar-static-top .navbar-brand-text { - display: none; - } - #content { - margin-left: 160px; - } - #content #fold-button { - display: none; - } - #content .navbar-main, - #content .navbar-main-additional { - left: 160px; - } - .table td.td-long { - width: 20%; - } -} -@media (min-width: 1280px) { - #sidebar { - left: 0; - } - #content { - margin-left: 250px; - } - #content #fold-button { - display: none; - } - #content .navbar-main, - #content .navbar-main-additional { - left: 250px; - } - .table td.td-long { - width: 30%; - } -} -#total-mem { - background-color: #7cb5ec; -} -#heap-mem { - background-color: #434348; -} -#non-heap-mem { - background-color: #90ed7d; -} -a.show-pointer { - cursor: pointer; -}
