YARN-4733. [YARN-3368] Initial commit of new YARN web UI. (wangda)

This commit is contained in:
Wangda Tan 2015-12-08 16:37:50 -08:00
parent d8bab3dcb6
commit 53e661f68e
104 changed files with 3518 additions and 0 deletions

View File

@ -0,0 +1,4 @@
{
"directory": "bower_components",
"analytics": false
}

View File

@ -0,0 +1,34 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 2
[*.hbs]
insert_final_newline = false
indent_style = space
indent_size = 2
[*.css]
indent_style = space
indent_size = 2
[*.html]
indent_style = space
indent_size = 2
[*.{diff,md}]
trim_trailing_whitespace = false

View File

@ -0,0 +1,11 @@
{
/**
Ember CLI sends analytics information by default. The data is completely
anonymous, but there are times when you might want to disable this behavior.
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"disableAnalytics": false,
"liveReload": true,
"watcher": "polling"
}

View File

@ -0,0 +1,17 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log

View File

@ -0,0 +1,32 @@
{
"predef": [
"document",
"window",
"-Promise"
],
"browser": true,
"boss": true,
"curly": true,
"debug": false,
"devel": true,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"esnext": true,
"unused": true
}

View File

@ -0,0 +1,23 @@
---
language: node_js
node_js:
- "0.12"
sudo: false
cache:
directories:
- node_modules
before_install:
- export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
- "npm config set spin false"
- "npm install -g npm@^2"
install:
- npm install -g bower
- npm install
- bower install
script:
- npm test

View File

@ -0,0 +1,3 @@
{
"ignore_dirs": ["tmp"]
}

View File

@ -0,0 +1,24 @@
# Yarn-ui
*This is a WIP project, nobody should use it in production.*
## Prerequisites
You will need the following things properly installed on your computer.
* Install Node.js with NPM: https://nodejs.org/download/
* After Node.js installed, install bower: `npm install -g bower`.
* Install Ember-cli: `npm install -g ember-cli`
## Installation
* Goto root directory of yarn-ui project: `hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui`
* `npm install && bower install`, it will take a while to finish.
## Try it
* Packaging and deploying Hadoop in this branch (You can use latest trunk after YARN-4417 committed to trunk)
* Modify `app/adapters/yarn-app.js`, change `host` to your YARN RM web address
* If you running YARN RM in your localhost, you should install `npm install -g corsproxy` and run `corsproxy` to avoid CORS errors. More details: `https://www.npmjs.com/package/corsproxy`. And the `host` of `app/adapters/yarn-app.js` should start with `localhost:1337`.
* Run `ember server`
* Visit your app at [http://localhost:4200](http://localhost:4200).

View File

@ -0,0 +1,19 @@
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
host: 'http://localhost:1337/localhost:8088', // configurable
namespace: 'ws/v1/cluster', // common const
pathForType(modelName) {
return ''; // move to some common place, return path by modelname.
},
ajax(url, method, hash) {
hash = hash || {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,19 @@
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
host: 'http://localhost:1337/localhost:8088', // configurable
namespace: 'ws/v1/cluster/metrics', // common const
pathForType(modelName) {
return ''; // move to some common place, return path by modelname.
},
ajax(url, method, hash) {
hash = hash || {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,31 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
host: 'http://localhost:1337/localhost:8088', // configurable
namespace: 'ws/v1/cluster', // common const
urlForQuery(query, modelName) {
var url = this._buildURL();
return url + '/apps/' + query.appId + "/appattempts";
},
urlForFindRecord(id, modelName, snapshot) {
var url = this._buildURL();
var url = url + '/apps/' +
Converter.attemptIdToAppId(id) + "/appattempts/" + id;
console.log(url);
return url;
},
ajax(url, method, hash) {
hash = {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,25 @@
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
host: 'http://localhost:1337/localhost:8088', // configurable
namespace: 'ws/v1/cluster', // common const
pathForType(modelName) {
return 'apps'; // move to some common place, return path by modelname.
},
/*
urlForQuery(query, modelName) {
var url = this._buildURL();
return url + '/apps/' + query.appId + "/appattempts";
},
*/
ajax(url, method, hash) {
hash = hash || {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,42 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
rmHost: 'http://localhost:1337/localhost:8088',
tsHost: 'http://localhost:1337/localhost:8188',
host: function() {
return undefined
}.property(),
rmNamespace: 'ws/v1/cluster',
tsNamespace: 'ws/v1/applicationhistory',
namespace: function() {
return undefined
}.property(),
urlForQuery(query, modelName) {
if (query.is_rm) {
this.set("host", this.rmHost);
this.set("namespace", this.rmNamespace);
} else {
this.set("host", this.tsHost);
this.set("namespace", this.tsNamespace);
}
var url = this._buildURL();
url = url + '/apps/' + Converter.attemptIdToAppId(query.app_attempt_id)
+ "/appattempts/" + query.app_attempt_id + "/containers";
console.log(url);
return url;
},
ajax(url, method, hash) {
hash = {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,19 @@
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
headers: {
Accept: 'application/json'
},
host: 'http://localhost:1337/localhost:8088', // configurable
namespace: 'ws/v1/cluster', // common const
pathForType(modelName) {
return 'scheduler'; // move to some common place, return path by modelname.
},
ajax(url, method, hash) {
hash = hash || {};
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
hash.targetServer = "RM";
return this._super(url, method, hash);
}
});

View File

@ -0,0 +1,20 @@
import Ember from 'ember';
import Resolver from 'ember/resolver';
import loadInitializers from 'ember/load-initializers';
import config from './config/environment';
import Sorter from 'yarn-ui/utils/sorter';
var App;
Ember.MODEL_FACTORY_INJECTIONS = true;
App = Ember.Application.extend({
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver: Resolver
});
loadInitializers(App, config.modulePrefix);
Sorter.initDataTableSorter();
export default App;

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
});

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
});

View File

@ -0,0 +1,104 @@
import Ember from 'ember';
import BaseChartComponent from 'yarn-ui/components/base-chart-component';
export default BaseChartComponent.extend({
// data:
// [{label=label1, value=value1}, ...]
// ...
renderBarChart: function(data, title, textWidth = 50) {
var g = this.chart.g;
var layout = this.getLayout();
this.renderTitleAndBG(g, title, layout);
var maxValue = -1;
for (var i = 0; i < data.length; i++) {
if (data[i] instanceof Array) {
if (data[i][0].value > maxValue) {
maxValue = data[i][0].value;
}
} else {
if (data[i].value > maxValue) {
maxValue = data[i].value;
}
}
}
var singleBarHeight = 30;
// 50 is for text
var maxBarWidth = layout.x2 - layout.x1 - 2 * layout.margin - textWidth - 50;
// 30 is for title
var maxBarsHeight = layout.y2 - layout.y1 - 2 * layout.margin - 30;
var gap = (maxBarsHeight - data.length * singleBarHeight) / (data.length -
1);
var xScaler = d3.scale.linear()
.domain([0, maxValue])
.range([0, maxBarWidth]);
// show bar text
for (var i = 0; i < data.length; i++) {
g.append("text")
.text(
function() {
return data[i].label;
})
.attr("y", function() {
return layout.y1 + singleBarHeight / 2 + layout.margin + (gap +
singleBarHeight) * i + 30;
})
.attr("x", layout.x1 + layout.margin);
}
// show bar
var bar = g.selectAll("bars")
.data(data)
.enter()
.append("rect")
.attr("y", function(d, i) {
return layout.y1 + 30 + layout.margin + (gap + singleBarHeight) * i;
})
.attr("x", layout.x1 + layout.margin + textWidth)
.attr("height", singleBarHeight)
.attr("fill", function(d, i) {
return this.colors[i];
}.bind(this))
.attr("width", 0);
this.bindTooltip(bar);
bar.transition()
.duration(500)
.attr("width", function(d) {
var w;
w = xScaler(d.value);
// At least each item has 3 px
w = Math.max(w, 3);
return w;
});
// show bar value
for (var i = 0; i < data.length; i++) {
g.append("text")
.text(
function() {
return data[i].value;
})
.attr("y", function() {
return layout.y1 + singleBarHeight / 2 + layout.margin + (gap +
singleBarHeight) * i + 30;
})
.attr("x", layout.x1 + layout.margin + textWidth + 15 + xScaler(data[i].value));
}
},
draw: function() {
this.initChart();
this.renderBarChart(this.get("data"), this.get("title"), this.get("textWidth"));
},
didInsertElement: function() {
this.draw();
},
})

View File

@ -0,0 +1,109 @@
import Ember from 'ember';
export default Ember.Component.extend({
chart: undefined,
tooltip : undefined,
colors: d3.scale.category10().range(),
initChart: function() {
this.chart = {
svg: undefined,
g: undefined,
h: 0,
w: 0,
tooltip: undefined
};
// Init tooltip if it is not initialized
this.tooltip = d3.select("#chart-tooltip");
if (!this.tooltip) {
this.tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.attr("id", "chart-tooltip")
.style("opacity", 0);
}
// Init svg
var svg = this.chart.svg;
if (svg) {
svg.remove();
}
var parentId = this.get("parentId");
var parent = d3.select("#" + parentId);
var bbox = parent.node().getBoundingClientRect();
this.chart.w = bbox.width - 30;
var ratio = 0.75; // 4:3 by default
if (this.get("ratio")) {
ratio = this.get("ratio");
}
this.chart.h = bbox.width * ratio;
if (this.get("maxHeight")) {
this.chart.h = Math.min(this.get("maxHeight"), this.chart.h);
}
this.chart.svg = parent.append("svg")
.attr("width", this.chart.w)
.attr("height", this.chart.h);
this.chart.g = this.chart.svg.append("g");
},
renderTitleAndBG: function(g, title, layout) {
var bg = g.append("g");
bg.append("text")
.text(title)
.attr("x", (layout.x1 + layout.x2) / 2)
.attr("y", layout.y1 + layout.margin + 20)
.attr("class", "chart-title");
bg.append("rect")
.attr("x", layout.x1)
.attr("y", layout.y1)
.attr("width", layout.x2 - layout.x1)
.attr("height", layout.y2 - layout.y1)
.attr("class", "chart-frame");
},
bindTooltip: function(d) {
d.on("mouseover", function(d) {
this.tooltip
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mousemove", function(d) {
// Handle pie chart case
var data = d;
if (d.data) {
data = d.data;
}
this.tooltip.style("opacity", .9);
this.tooltip.html(data.label + " = " + data.value)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mouseout", function(d) {
this.tooltip.style("opacity", 0);
}.bind(this));
},
getLayout: function() {
var x1 = 0;
var y1 = 0;
var x2 = this.chart.w;
var y2 = this.chart.h;
var layout = {
x1: x1,
y1: y1,
x2: x2 - 10,
y2: y2 - 10,
margin: 10
};
return layout;
},
});

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
});

View File

@ -0,0 +1,148 @@
import Ember from 'ember';
import BaseChartComponent from 'yarn-ui/components/base-chart-component';
export default BaseChartComponent.extend({
/*
* data = [{label="xx", value=},{...}]
*/
renderDonutChart: function(data, title, showLabels = false,
middleLabel = "Total", middleValue = undefined) {
var g = this.chart.g;
var layout = this.getLayout();
this.renderTitleAndBG(g, title, layout);
var total = 0;
var allZero = true;
for (var i = 0; i < data.length; i++) {
total += data[i].value;
if (data[i].value > 1e-6) {
allZero = false;
}
}
if (!middleValue) {
middleValue = total;
}
//Width and height
var h = layout.y2 - layout.y1;
// 50 is for title
var outerRadius = (h - 50 - 2 * layout.margin) / 2;
var innerRadius = outerRadius * 0.618;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var cx;
var cy = layout.y1 + 50 + layout.margin + outerRadius;
if (showLabels) {
cx = layout.x1 + layout.margin + outerRadius;
} else {
cx = (layout.x1 + layout.x2) / 2;
}
var pie = d3.layout.pie();
pie.sort(null);
pie.value(function(d) {
var v = d.value;
// make sure it > 0
v = Math.max(v, 1e-6);
return v;
});
//Set up groups
var arcs = g
.selectAll("g.arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + cx + "," + cy + ")");
function tweenPie(finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) {
return arc(i(d));
};
}
//Draw arc paths
var path = arcs.append("path")
.attr("fill", function(d, i) {
if (d.value > 1e-6) {
return this.colors[i];
} else {
return "white";
}
}.bind(this))
.attr("d", arc)
.attr("stroke", function(d, i) {
if (allZero) {
return this.colors[i];
}
}.bind(this))
.attr("stroke-dasharray", function(d, i) {
if (d.value <= 1e-6) {
return "10,10";
}
}.bind(this));
this.bindTooltip(path);
// Show labels
if (showLabels) {
var lx = layout.x1 + layout.margin + outerRadius * 2 + 30;
var squareW = 15;
var margin = 10;
var select = g.selectAll(".rect")
.data(data)
.enter();
select.append("rect")
.attr("fill", function(d, i) {
return this.colors[i];
}.bind(this))
.attr("x", lx)
.attr("y", function(d, i) {
return layout.y1 + 50 + (squareW + margin) * i + layout.margin;
})
.attr("width", squareW)
.attr("height", squareW);
select.append("text")
.attr("x", lx + squareW + margin)
.attr("y", function(d, i) {
return layout.y1 + 50 + (squareW + margin) * i + layout.margin + squareW / 2;
})
.text(function(d) {
return d.label + ' = ' + d.value;
});
}
if (middleLabel) {
var highLightColor = this.colors[0];
g.append("text").text(middleLabel).attr("x", cx).attr("y", cy - 10).
attr("class", "donut-highlight-text").attr("fill", highLightColor);
g.append("text").text(middleValue).attr("x", cx).attr("y", cy + 20).
attr("class", "donut-highlight-text").attr("fill", highLightColor).
style("font-size", "30px");
}
path.transition()
.duration(500)
.attrTween('d', tweenPie);
},
draw: function() {
this.initChart();
this.renderDonutChart(this.get("data"), this.get("title"), this.get("showLabels"),
this.get("middleLabel"), this.get("middleValue"));
},
didInsertElement: function() {
this.draw();
},
})

View File

@ -0,0 +1,21 @@
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function() {
$(".js-example-basic-single").select2(
{
width: '100%',
placeholder: "Select a queue"
});
var elementId = this.get("element-id");
var prefix = this.get("prefix");
var element = d3.select("#" + elementId);
if (element) {
this.get("model").forEach(function(o) {
element.append("option").attr("value", o.get("name")).text(prefix + o.get("name"));
});
}
}
});

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
});

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
});

View File

@ -0,0 +1,272 @@
import Ember from 'ember';
import ChartUtilsMixin from 'yarn-ui/mixins/charts-utils';
export default Ember.Component.extend(ChartUtilsMixin, {
queues: {
data: undefined,
foldedQueues: {},
selectedQueueCircle: undefined,
maxDepth: -1,
},
queueColors: d3.scale.category20().range(),
renderQueue: function(now, depth, sequence) {
if (depth > this.queues.maxDepth) {
this.queues.maxDepth = depth;
}
var cx = 20 + depth * 30;
var cy = 20 + sequence * 30;
var name = now.get("name");
var g = this.queues.dataGroup.append("g")
.attr("id", "queue-" + name + "-g");
var folded = this.queues.foldedQueues[name];
var isParentQueue = false;
// render its children
var children = [];
var childrenNames = now.get("children");
if (childrenNames) {
childrenNames.forEach(function(name) {
isParentQueue = true;
var child = this.queues.data[name];
if (child) {
children.push(child);
}
}.bind(this));
}
if (folded) {
children = [];
}
var linefunction = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
for (var i = 0; i < children.length; i++) {
sequence = sequence + 1;
// Get center of children queue
var cc = this.renderQueue(children[i],
depth + 1, sequence);
g.append("path")
.attr("class", "queue")
.attr("d", linefunction([{
x: cx,
y: cy
}, {
x: cc.x - 20,
y: cc.y
}, cc]));
}
var circle = g.append("circle")
.attr("cx", cx)
.attr("cy", cy)
.attr("class", "queue");
circle.on('mouseover', function() {
circle.style("fill", this.queueColors[1]);
}.bind(this));
circle.on('mouseout', function() {
if (circle != this.queues.selectedQueueCircle) {
circle.style("fill", this.queueColors[0]);
}
}.bind(this));
circle.on('click', function() {
circle.style("fill", this.queueColors[2]);
var pre = this.queues.selectedQueueCircle;
this.queues.selectedQueueCircle = circle;
if (pre) {
pre.on('mouseout')();
}
this.renderCharts(name);
}.bind(this));
circle.on('dblclick', function() {
if (!isParentQueue) {
return;
}
if (this.queues.foldedQueues[name]) {
delete this.queues.foldedQueues[name];
} else {
this.queues.foldedQueues[name] = now;
}
this.renderQueues();
}.bind(this));
var text = name;
if (folded) {
text = name + " (+)";
}
// print queue's name
g.append("text")
.attr("x", cx + 30)
.attr("y", cy + 5)
.text(text)
.attr("class", "queue");
return {
x: cx,
y: cy
};
},
renderQueues: function() {
if (this.queues.dataGroup) {
this.queues.dataGroup.remove();
}
// render queues
this.queues.dataGroup = this.canvas.svg.append("g")
.attr("id", "queues-g");
var rootQueue = undefined;
if (this.queues.data) {
this.renderQueue(this.queues.data['root'], 0, 0);
}
},
draw: function() {
this.queues.data = {};
this.get("model")
.forEach(function(o) {
this.queues.data[o.id] = o;
}.bind(this));
// get w/h of the svg
var bbox = d3.select("#main-container")
.node()
.getBoundingClientRect();
this.canvas.w = bbox.width;
this.canvas.h = Math.max(Object.keys(this.queues.data)
.length * 35, 1500);
this.canvas.svg = d3.select("#main-container")
.append("svg")
.attr("width", this.canvas.w)
.attr("height", this.canvas.h)
.attr("id", "main-svg");
this.renderBackground();
this.renderQueues();
this.renderCharts("root");
},
didInsertElement: function() {
this.draw();
},
/*
* data = [{label="xx", value=},{...}]
*/
renderTable: function(data, title, layout) {
d3.select("#main-svg")
.append('table')
.selectAll('tr')
.data(data)
.enter()
.append('tr')
.selectAll('td')
.data(function(d) {
return d;
})
.enter()
.append('td')
.text(function(d) {
return d;
});
},
renderQueueCapacities: function(queue, layout) {
// Render bar chart
this.renderBarChart(this.charts.g, [{
label: "Cap",
value: queue.get("capacity")
}, {
label: "MaxCap",
value: queue.get("maxCapacity")
}, {
label: "UsedCap",
value: queue.get("usedCapacity")
}], "Queue Capacities", layout, 60);
},
renderChildrenCapacities: function(queue, layout) {
var data = [];
var children = queue.get("children");
if (children) {
for (var i = 0; i < children.length; i++) {
var child = this.queues.data[children[i]];
data.push({
label: child.get("name"),
value: child.get("capacity")
});
}
}
this.renderDonutChart(this.charts.g, data, "Children Capacities", layout, true);
},
renderChildrenUsedCapacities: function(queue, layout) {
var data = [];
var children = queue.get("children");
if (children) {
for (var i = 0; i < children.length; i++) {
var child = this.queues.data[children[i]];
data.push({
label: child.get("name"),
value: child.get("usedCapacity")
});
}
}
this.renderDonutChart(this.charts.g, data, "Children Used Capacities", layout, true);
},
renderLeafQueueUsedCapacities: function(layout) {
var leafQueueUsedCaps = [];
for (var queueName in this.queues.data) {
var q = this.queues.data[queueName];
if ((!q.get("children")) || q.get("children")
.length == 0) {
// it's a leafqueue
leafQueueUsedCaps.push({
label: q.get("name"),
value: q.get("usedCapacity")
});
}
}
this.renderDonutChart(this.charts.g, leafQueueUsedCaps, "LeafQueues Used Capacities",
layout, true);
},
renderCharts: function(queueName) {
this.charts.leftBannerLen = this.queues.maxDepth * 30 + 100;
this.initCharts();
var queue = this.queues.data[queueName];
var idx = 0;
if (queue.get("name") == "root") {
this.renderLeafQueueUsedCapacities(this.getLayout(idx++));
}
if (queue.get("name") != "root") {
this.renderQueueCapacities(queue, this.getLayout(idx++));
}
if (queue.get("children") && queue.get("children")
.length > 0) {
this.renderChildrenCapacities(queue, this.getLayout(idx++));
this.renderChildrenUsedCapacities(queue, this.getLayout(idx++));
}
},
});

View File

@ -0,0 +1,30 @@
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function() {
var paging = this.get("paging") ? true : this.get("paging");
var ordering = this.get("ordering") ? true : this.get("ordering");
var info = this.get("info") ? true : this.get("info");
var bFilter = this.get("bFilter") ? true : this.get("bFilter");
var colDefs = [];
if (this.get("colTypes")) {
var typesArr = this.get("colTypes").split(' ');
var targetsArr = this.get("colTargets").split(' ');
for (var i = 0; i < typesArr.length; i++) {
colDefs.push({
type: typesArr[i],
targets: parseInt(targetsArr[i])
});
}
}
$('#' + this.get('table-id')).DataTable({
"paging": paging,
"ordering": ordering,
"info": info,
"bFilter": bFilter,
columnDefs: colDefs
});
}
});

View File

@ -0,0 +1,250 @@
import Ember from 'ember';
import Converter from 'yarn-ui/utils/converter';
export default Ember.Component.extend({
canvas: {
svg: undefined,
h: 0,
w: 0,
tooltip: undefined
},
clusterMetrics: undefined,
modelArr: [],
colors: d3.scale.category10().range(),
_selected: undefined,
selected: function() {
return this._selected;
}.property(),
tableComponentName: function() {
return "app-attempt-table";
}.property(),
setSelected: function(d) {
if (this._selected == d) {
return;
}
// restore color
if (this._selected) {
var dom = d3.select("#timeline-bar-" + this._selected.get("id"));
dom.attr("fill", this.colors[0]);
}
this._selected = d;
this.set("selected", d);
dom = d3.select("#timeline-bar-" + d.get("id"));
dom.attr("fill", this.colors[1]);
},
getPerItemHeight: function() {
var arrSize = this.modelArr.length;
if (arrSize < 20) {
return 30;
} else if (arrSize < 100) {
return 10;
} else {
return 2;
}
},
getPerItemGap: function() {
var arrSize = this.modelArr.length;
if (arrSize < 20) {
return 5;
} else if (arrSize < 100) {
return 1;
} else {
return 1;
}
},
getCanvasHeight: function() {
return (this.getPerItemHeight() + this.getPerItemGap()) * this.modelArr.length + 200;
},
draw: function(start, end) {
// get w/h of the svg
var bbox = d3.select("#" + this.get("parent-id"))
.node()
.getBoundingClientRect();
this.canvas.w = bbox.width;
this.canvas.h = this.getCanvasHeight();
this.canvas.svg = d3.select("#" + this.get("parent-id"))
.append("svg")
.attr("width", this.canvas.w)
.attr("height", this.canvas.h)
.attr("id", this.get("my-id"));
this.renderTimeline(start, end);
},
renderTimeline: function(start, end) {
var border = 30;
var singleBarHeight = this.getPerItemHeight();
var gap = this.getPerItemGap();
var textWidth = 50;
/*
start-time end-time
|--------------------------------------|
==============
==============
==============
===============
*/
var xScaler = d3.scale.linear()
.domain([start, end])
.range([0, this.canvas.w - 2 * border - textWidth]);
/*
* Render frame of timeline view
*/
this.canvas.svg.append("line")
.attr("x1", border + textWidth)
.attr("y1", border - 5)
.attr("x2", this.canvas.w - border)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("line")
.attr("x1", border + textWidth)
.attr("y1", border - 10)
.attr("x2", border + textWidth)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("line")
.attr("x1", this.canvas.w - border)
.attr("y1", border - 10)
.attr("x2", this.canvas.w - border)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("text")
.text(Converter.timeStampToDate(start))
.attr("y", border - 15)
.attr("x", border + textWidth)
.attr("class", "bar-chart-text")
.attr("text-anchor", "left");
this.canvas.svg.append("text")
.text(Converter.timeStampToDate(end))
.attr("y", border - 15)
.attr("x", this.canvas.w - border)
.attr("class", "bar-chart-text")
.attr("text-anchor", "end");
// show bar
var bar = this.canvas.svg.selectAll("bars")
.data(this.modelArr)
.enter()
.append("rect")
.attr("y", function(d, i) {
return border + (gap + singleBarHeight) * i;
})
.attr("x", function(d, i) {
return border + textWidth + xScaler(d.get("startTs"));
})
.attr("height", singleBarHeight)
.attr("fill", function(d, i) {
return this.colors[0];
}.bind(this))
.attr("width", function(d, i) {
var finishedTs = xScaler(d.get("finishedTs"));
finishedTs = finishedTs > 0 ? finishedTs : xScaler(end);
return finishedTs - xScaler(d.get("startTs"));
})
.attr("id", function(d, i) {
return "timeline-bar-" + d.get("id");
});
bar.on("click", function(d) {
this.setSelected(d);
}.bind(this));
this.bindTooltip(bar);
if (this.modelArr.length <= 20) {
// show bar texts
for (var i = 0; i < this.modelArr.length; i++) {
this.canvas.svg.append("text")
.text(this.modelArr[i].get(this.get("label")))
.attr("y", border + (gap + singleBarHeight) * i + singleBarHeight / 2)
.attr("x", border)
.attr("class", "bar-chart-text");
}
}
},
bindTooltip: function(d) {
d.on("mouseover", function(d) {
this.tooltip
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mousemove", function(d) {
this.tooltip.style("opacity", .9);
this.tooltip.html(d.get("tooltipLabel"))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mouseout", function(d) {
this.tooltip.style("opacity", 0);
}.bind(this));
},
initTooltip: function() {
this.tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.attr("id", "chart-tooltip")
.style("opacity", 0);
},
didInsertElement: function() {
// init tooltip
this.initTooltip();
// init model
if (this.get("rmModel")) {
this.get("rmModel").forEach(function(o) {
this.modelArr.push(o);
}.bind(this));
}
if (this.get("tsModel")) {
this.get("tsModel").forEach(function(o) {
this.modelArr.push(o);
}.bind(this));
}
this.modelArr.sort(function(a, b) {
var tsA = a.get("startTs");
var tsB = b.get("startTs");
return tsA - tsB;
});
if (this.modelArr.length > 0) {
var begin = this.modelArr[0].get("startTs");
}
var end = 0;
for (var i = 0; i < this.modelArr.length; i++) {
var ts = this.modelArr[i].get("finishedTs");
if (ts > end) {
end = ts;
}
}
if (end < begin) {
end = Date.now();
}
this.draw(begin, end);
if (this.modelArr.length > 0) {
this.setSelected(this.modelArr[0]);
}
},
});

View File

@ -0,0 +1,257 @@
import Ember from 'ember';
export default Ember.Component.extend({
// Map: <queue-name, queue>
map : undefined,
// Normalized data for d3
treeData: undefined,
// folded queues, folded[<queue-name>] == true means <queue-name> is folded
foldedQueues: { },
// maxDepth
maxDepth: 0,
// num of leaf queue, folded queue is treated as leaf queue
numOfLeafQueue: 0,
// mainSvg
mainSvg: undefined,
// Init data
initData: function() {
this.map = { };
this.treeData = { };
this.maxDepth = 0;
this.numOfLeafQueue = 0;
this.get("model")
.forEach(function(o) {
this.map[o.id] = o;
}.bind(this));
var selected = this.get("selected");
this.initQueue("root", 1, this.treeData);
},
// get Children array of given queue
getChildrenNamesArray: function(q) {
var namesArr = [];
// Folded queue's children is empty
if (this.foldedQueues[q.get("name")]) {
return namesArr;
}
var names = q.get("children");
if (names) {
names.forEach(function(name) {
namesArr.push(name);
});
}
return namesArr;
},
// Init queues
initQueue: function(queueName, depth, node) {
if ((!queueName) || (!this.map[queueName])) {
// Queue is not existed
return;
}
if (depth > this.maxDepth) {
this.maxDepth = this.maxDepth + 1;
}
var queue = this.map[queueName];
var names = this.getChildrenNamesArray(queue);
node.name = queueName;
node.parent = queue.get("parent");
node.queueData = queue;
if (names.length > 0) {
node.children = [];
names.forEach(function(name) {
var childQueueData = {};
node.children.push(childQueueData);
this.initQueue(name, depth + 1, childQueueData);
}.bind(this));
} else {
this.numOfLeafQueue = this.numOfLeafQueue + 1;
}
},
update: function(source, root, tree, diagonal) {
var duration = 300;
var i = 0;
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 200; });
// Update the nodes…
var node = this.mainSvg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", function(d,i){
if (d.queueData.get("name") != this.get("selected")) {
document.location.href = "yarnQueue/" + d.queueData.get("name");
}
}.bind(this));
// .on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
var usedCap = d.queueData.get("usedCapacity");
if (usedCap <= 60.0) {
return "LimeGreen";
} else if (usedCap <= 100.0) {
return "DarkOrange";
} else {
return "LightCoral";
}
});
// append percentage
nodeEnter.append("text")
.attr("x", function(d) { return 0; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return "middle"; })
.text(function(d) {
var usedCap = d.queueData.get("usedCapacity");
if (usedCap >= 100.0) {
return usedCap.toFixed(0) + "%";
} else {
return usedCap.toFixed(1) + "%";
}
})
.style("fill-opacity", 1e-6);
// append queue name
nodeEnter.append("text")
.attr("x", function(d) { return 40; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 20)
.attr("href",
function(d) {
return "yarnQueues/" + d.queueData.get("name");
})
.style("stroke", function(d) {
if (d.queueData.get("name") == this.get("selected")) {
return "red";
} else {
return "gray";
}
}.bind(this));
nodeUpdate.selectAll("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = this.mainSvg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
},
reDraw: function() {
this.initData();
var margin = {top: 20, right: 120, bottom: 20, left: 120};
var treeWidth = this.maxDepth * 200;
var treeHeight = this.numOfLeafQueue * 80;
var width = treeWidth + margin.left + margin.right;
var height = treeHeight + margin.top + margin.bottom;
var layout = { };
if (this.mainSvg) {
this.mainSvg.remove();
}
this.mainSvg = d3.select("#" + this.get("parentId")).append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "tree-selector")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tree = d3.layout.tree().size([treeHeight, treeWidth]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var root = this.treeData;
root.x0 = height / 2;
root.y0 = 0;
d3.select(self.frameElement).style("height", height);
this.update(root, root, tree, diagonal);
},
didInsertElement: function() {
this.reDraw();
}
});

View File

@ -0,0 +1,5 @@
import Ember from 'ember';
export default Ember.Controller.extend({
loading: true,
});

View File

@ -0,0 +1,4 @@
import Ember from 'ember';
export default Ember.Controller.extend({
});

View File

@ -0,0 +1,6 @@
import Ember from 'ember';
export default Ember.Controller.extend({
needReload: true,
selectedQueue: undefined,
});

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>YarnUi</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for 'head'}}
<link rel="stylesheet" href="assets/vendor.css">
<link rel="stylesheet" href="assets/yarn-ui.css">
{{content-for 'head-footer'}}
</head>
<body>
{{content-for 'body'}}
<script src="assets/vendor.js"></script>
<script src="assets/yarn-ui.js"></script>
{{content-for 'body-footer'}}
</body>
</html>

View File

@ -0,0 +1,13 @@
import DS from 'ember-data';
export default DS.Model.extend({
startedOn: DS.attr('string'),
state: DS.attr('string'),
haState: DS.attr('string'),
rmStateStoreName: DS.attr('string'),
resourceManagerVersion: DS.attr('string'),
resourceManagerBuildVersion: DS.attr('string'),
hadoopVersion: DS.attr('string'),
hadoopBuildVersion: DS.attr('string'),
hadoopVersionBuiltOn: DS.attr('string')
});

View File

@ -0,0 +1,115 @@
import DS from 'ember-data';
export default DS.Model.extend({
appsSubmitted: DS.attr('number'),
appsCompleted: DS.attr('number'),
appsPending: DS.attr('number'),
appsRunning: DS.attr('number'),
appsFailed: DS.attr('number'),
appsKilled: DS.attr('number'),
reservedMB: DS.attr('number'),
availableMB: DS.attr('number'),
allocatedMB: DS.attr('number'),
reservedVirtualCores: DS.attr('number'),
availableVirtualCores: DS.attr('number'),
allocatedVirtualCores: DS.attr('number'),
containersAllocated: DS.attr('number'),
containersReserved: DS.attr('number'),
containersPending: DS.attr('number'),
totalMB: DS.attr('number'),
totalVirtualCores: DS.attr('number'),
totalNodes: DS.attr('number'),
lostNodes: DS.attr('number'),
unhealthyNodes: DS.attr('number'),
decommissionedNodes: DS.attr('number'),
rebootedNodes: DS.attr('number'),
activeNodes: DS.attr('number'),
getFinishedAppsDataForDonutChart: function() {
var arr = [];
arr.push({
label: "Completed",
value: this.get("appsCompleted")
});
arr.push({
label: "Killed",
value: this.get("appsKilled")
});
arr.push({
label: "Failed",
value: this.get("appsFailed")
});
return arr;
}.property("appsCompleted", "appsKilled", "appsFailed"),
getRunningAppsDataForDonutChart: function() {
var arr = [];
arr.push({
label: "Pending",
value: this.get("appsPending")
});
arr.push({
label: "Running",
value: this.get("appsRunning")
});
return arr;
}.property("appsPending", "appsRunning"),
getNodesDataForDonutChart: function() {
var arr = [];
arr.push({
label: "Active",
value: this.get("activeNodes")
});
arr.push({
label: "Unhealthy",
value: this.get("unhealthyNodes")
});
arr.push({
label: "Decomissioned",
value: this.get("decommissionedNodes")
});
return arr;
}.property("activeNodes", "unhealthyNodes", "decommissionedNodes"),
getMemoryDataForDonutChart: function() {
var type = "MB";
var arr = [];
arr.push({
label: "Allocated",
value: this.get("allocated" + type)
});
arr.push({
label: "Reserved",
value: this.get("reserved" + type)
});
arr.push({
label: "Available",
value: this.get("available" + type)
});
return arr;
}.property("allocatedMB", "reservedMB", "availableMB"),
getVCoreDataForDonutChart: function() {
var type = "VirtualCores";
var arr = [];
arr.push({
label: "Allocated",
value: this.get("allocated" + type)
});
arr.push({
label: "Reserved",
value: this.get("reserved" + type)
});
arr.push({
label: "Available",
value: this.get("available" + type)
});
return arr;
}.property("allocatedVirtualCores", "reservedVirtualCores", "availableVirtualCores"),
});

View File

@ -0,0 +1,44 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.Model.extend({
startTime: DS.attr('string'),
finishedTime: DS.attr('string'),
containerId: DS.attr('string'),
nodeHttpAddress: DS.attr('string'),
nodeId: DS.attr('string'),
logsLink: DS.attr('string'),
startTs: function() {
return Converter.dateToTimeStamp(this.get("startTime"));
}.property("startTime"),
finishedTs: function() {
var ts = Converter.dateToTimeStamp(this.get("finishedTime"));
return ts;
}.property("finishedTime"),
shortAppAttemptId: function() {
return "attempt_" +
parseInt(Converter.containerIdToAttemptId(this.get("containerId")).split("_")[3]);
}.property("containerId"),
elapsedTime: function() {
var elapsedMs = this.get("finishedTs") - this.get("startTs");
if (elapsedMs <= 0) {
elapsedMs = Date.now() - this.get("startTs");
}
return Converter.msToElapsedTime(elapsedMs);
}.property(),
tooltipLabel: function() {
return "<p>Id:" + this.get("id") +
"</p><p>ElapsedTime:" +
String(this.get("elapsedTime")) + "</p>";
}.property(),
link: function() {
return "/yarnAppAttempt/" + this.get("id");
}.property(),
});

View File

@ -0,0 +1,65 @@
import Converter from 'yarn-ui/utils/converter';
import DS from 'ember-data';
export default DS.Model.extend({
appName: DS.attr('string'),
user: DS.attr('string'),
queue: DS.attr('string'),
state: DS.attr('string'),
startTime: DS.attr('string'),
elapsedTime: DS.attr('string'),
finalStatus: DS.attr('string'),
finishedTime: DS.attr('finishedTime'),
progress: DS.attr('number'),
diagnostics: DS.attr('string'),
amContainerLogs: DS.attr('string'),
amHostHttpAddress: DS.attr('string'),
logAggregationStatus: DS.attr('string'),
unmanagedApplication: DS.attr('string'),
amNodeLabelExpression: DS.attr('string'),
applicationTags: DS.attr('string'),
priority: DS.attr('number'),
allocatedMB: DS.attr('number'),
allocatedVCores: DS.attr('number'),
runningContainers: DS.attr('number'),
memorySeconds: DS.attr('number'),
vcoreSeconds: DS.attr('number'),
preemptedResourceMB: DS.attr('number'),
preemptedResourceVCores: DS.attr('number'),
numNonAMContainerPreempted: DS.attr('number'),
numAMContainerPreempted: DS.attr('number'),
isFailed: function() {
return this.get('finalStatus') == "FAILED"
}.property("finalStatus"),
allocatedResource: function() {
return Converter.resourceToString(this.get("allocatedMB"), this.get("allocatedVCores"));
}.property("allocatedMB", "allocatedVCores"),
preemptedResource: function() {
return Converter.resourceToString(this.get("preemptedResourceMB"), this.get("preemptedResourceVCores"));
}.property("preemptedResourceMB", "preemptedResourceVCores"),
aggregatedResourceUsage: function() {
return Converter.resourceToString(this.get("memorySeconds"), this.get("vcoreSeconds")) + " (× Secs)";
}.property("memorySeconds", "vcoreSeconds"),
progressStyle: function() {
return "width: " + this.get("progress") + "%";
}.property("progress"),
finalStatusStyle: function() {
var style = "default";
var finalStatus = this.get("finalStatus");
if (finalStatus == "KILLED") {
style = "warning";
} else if (finalStatus == "FAILED") {
style = "danger";
} else {
style = "success";
}
return "label label-" + style;
}.property("finalStatus")
});

View File

@ -0,0 +1,39 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.Model.extend({
allocatedMB: DS.attr('number'),
allocatedVCores: DS.attr('number'),
assignedNodeId: DS.attr('string'),
priority: DS.attr('number'),
startedTime: DS.attr('number'),
finishedTime: DS.attr('number'),
logUrl: DS.attr('string'),
containerExitStatus: DS.attr('number'),
containerState: DS.attr('string'),
nodeHttpAddress: DS.attr('string'),
startTs: function() {
return Converter.dateToTimeStamp(this.get("startedTime"));
}.property("startedTime"),
finishedTs: function() {
var ts = Converter.dateToTimeStamp(this.get("finishedTime"));
return ts;
}.property("finishedTime"),
elapsedTime: function() {
var elapsedMs = this.get("finishedTs") - this.get("startTs");
if (elapsedMs <= 0) {
elapsedMs = Date.now() - this.get("startTs");
}
return Converter.msToElapsedTime(elapsedMs);
}.property(),
tooltipLabel: function() {
return "<p>Id:" + this.get("id") +
"</p><p>ElapsedTime:" +
String(this.get("elapsedTime")) + "</p>";
}.property(),
});

View File

@ -0,0 +1,76 @@
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
children: DS.attr('array'),
parent: DS.attr('string'),
capacity: DS.attr('number'),
maxCapacity: DS.attr('number'),
usedCapacity: DS.attr('number'),
absCapacity: DS.attr('number'),
absMaxCapacity: DS.attr('number'),
absUsedCapacity: DS.attr('number'),
state: DS.attr('string'),
userLimit: DS.attr('number'),
userLimitFactor: DS.attr('number'),
preemptionDisabled: DS.attr('number'),
numPendingApplications: DS.attr('number'),
numActiveApplications: DS.attr('number'),
users: DS.hasMany('YarnUser'),
isLeafQueue: function() {
var len = this.get("children.length");
if (!len) {
return true;
}
return len <= 0;
}.property("children"),
capacitiesBarChartData: function() {
return [
{
label: "Absolute Capacity",
value: this.get("name") == "root" ? 100 : this.get("absCapacity")
},
{
label: "Absolute Used",
value: this.get("name") == "root" ? this.get("usedCapacity") : this.get("absUsedCapacity")
},
{
label: "Absolute Max Capacity",
value: this.get("name") == "root" ? 100 : this.get("absMaxCapacity")
}
]
}.property("absCapacity", "absUsedCapacity", "absMaxCapacity"),
userUsagesDonutChartData: function() {
var data = [];
if (this.get("users")) {
this.get("users").forEach(function(o) {
data.push({
label: o.get("name"),
value: o.get("usedMemoryMB")
})
});
}
return data;
}.property("users"),
hasUserUsages: function() {
return this.get("userUsagesDonutChartData").length > 0;
}.property(),
numOfApplicationsDonutChartData: function() {
return [
{
label: "Pending Apps",
value: this.get("numPendingApplications") || 0 // TODO, fix the REST API so root will return #applications as well.
},
{
label: "Active Apps",
value: this.get("numActiveApplications") || 0
}
]
}.property(),
});

View File

@ -0,0 +1,8 @@
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
queueName: DS.attr('string'),
usedMemoryMB: DS.attr('number'),
usedVCore: DS.attr('number')
})

View File

@ -0,0 +1,16 @@
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('yarnApps');
this.route('yarnQueue', { path: '/yarnQueue/:queue_name' });
this.route('clusterOverview');
this.route('yarnApp', { path: '/yarnApp/:app_id' });
this.route('yarnAppAttempt', { path: '/yarnAppAttempt/:app_attempt_id'});
});
export default Router;

View File

@ -0,0 +1,11 @@
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('ClusterMetric');
},
afterModel() {
this.controllerFor("ClusterOverview").set("loading", false);
}
});

View File

@ -0,0 +1,21 @@
import Ember from 'ember';
export default Ember.Route.extend({
model(param) {
return Ember.RSVP.hash({
attempt: this.store.findRecord('yarnAppAttempt', param.app_attempt_id),
rmContainers: this.store.query('yarnContainer',
{
app_attempt_id: param.app_attempt_id,
is_rm: true
}),
tsContainers: this.store.query('yarnContainer',
{
app_attempt_id: param.app_attempt_id,
is_rm: false
}),
});
}
});

View File

@ -0,0 +1,10 @@
import Ember from 'ember';
export default Ember.Route.extend({
model(param) {
return Ember.RSVP.hash({
app: this.store.find('yarnApp', param.app_id),
attempts: this.store.query('yarnAppAttempt', { appId: param.app_id})
});
}
});

View File

@ -0,0 +1,8 @@
import Ember from 'ember';
export default Ember.Route.extend({
model() {
var apps = this.store.findAll('yarnApp');
return apps
}
});

View File

@ -0,0 +1,20 @@
import Ember from 'ember';
export default Ember.Route.extend({
model(param) {
return Ember.RSVP.hash({
selected : param.queue_name,
queues: this.store.findAll('yarnQueue'),
selectedQueue : undefined,
apps: undefined, // apps of selected queue
});
},
afterModel(model) {
model.selectedQueue = this.store.peekRecord('yarnQueue', model.selected);
model.apps = this.store.findAll('yarnApp');
model.apps.forEach(function(o) {
console.log(o);
})
}
});

View File

@ -0,0 +1,5 @@
export default Ember.Route.extend({
beforeModel() {
this.transitionTo('yarnQueues.root');
}
});

View File

@ -0,0 +1,7 @@
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('yarnQueue');
},
});

View File

@ -0,0 +1,29 @@
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var fixedPayload = {
id: id,
type: primaryModelClass.modelName,
attributes: payload
};
return this._super(store, primaryModelClass, fixedPayload, id,
requestType);
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = [
this.normalizeSingleResponse(store, primaryModelClass,
payload.clusterInfo, payload.clusterInfo.id, requestType)
];
return normalizedArrayResponse;
}
});

View File

@ -0,0 +1,29 @@
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var fixedPayload = {
id: id,
type: primaryModelClass.modelName,
attributes: payload
};
return this._super(store, primaryModelClass, fixedPayload, id,
requestType);
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = [
this.normalizeSingleResponse(store, primaryModelClass,
payload.clusterMetrics, 1, requestType)
];
return normalizedArrayResponse;
}
});

View File

@ -0,0 +1,49 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.JSONAPISerializer.extend({
internalNormalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
if (payload.appAttempt) {
payload = payload.appAttempt;
}
var fixedPayload = {
id: payload.appAttemptId,
type: primaryModelClass.modelName, // yarn-app
attributes: {
startTime: Converter.timeStampToDate(payload.startTime),
finishedTime: Converter.timeStampToDate(payload.finishedTime),
containerId: payload.containerId,
nodeHttpAddress: payload.nodeHttpAddress,
nodeId: payload.nodeId,
state: payload.nodeId,
logsLink: payload.logsLink
}
};
return fixedPayload;
},
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var p = this.internalNormalizeSingleResponse(store,
primaryModelClass, payload, id, requestType);
return { data: p };
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = payload.appAttempts.appAttempt.map(singleApp => {
return this.internalNormalizeSingleResponse(store, primaryModelClass,
singleApp, singleApp.id, requestType);
}, this);
return normalizedArrayResponse;
}
});

View File

@ -0,0 +1,66 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.JSONAPISerializer.extend({
internalNormalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
if (payload.app) {
payload = payload.app;
}
var fixedPayload = {
id: id,
type: primaryModelClass.modelName, // yarn-app
attributes: {
appName: payload.name,
user: payload.user,
queue: payload.queue,
state: payload.state,
startTime: Converter.timeStampToDate(payload.startedTime),
elapsedTime: Converter.msToElapsedTime(payload.elapsedTime),
finishedTime: Converter.timeStampToDate(payload.finishedTime),
finalStatus: payload.finalStatus,
progress: payload.progress,
diagnostics: payload.diagnostics,
amContainerLogs: payload.amContainerLogs,
amHostHttpAddress: payload.amHostHttpAddress,
logAggregationStatus: payload.logAggregationStatus,
unmanagedApplication: payload.unmanagedApplication,
amNodeLabelExpression: payload.amNodeLabelExpression,
priority: payload.priority,
allocatedMB: payload.allocatedMB,
allocatedVCores: payload.allocatedVCores,
runningContainers: payload.runningContainers,
memorySeconds: payload.memorySeconds,
vcoreSeconds: payload.vcoreSeconds,
preemptedResourceMB: payload.preemptedResourceMB,
preemptedResourceVCores: payload.preemptedResourceVCores,
numNonAMContainerPreempted: payload.numNonAMContainerPreempted,
numAMContainerPreempted: payload.numAMContainerPreempted
}
};
return fixedPayload;
},
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var p = this.internalNormalizeSingleResponse(store,
primaryModelClass, payload, id, requestType);
return { data: p };
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = payload.apps.app.map(singleApp => {
return this.internalNormalizeSingleResponse(store, primaryModelClass,
singleApp, singleApp.id, requestType);
}, this);
return normalizedArrayResponse;
}
});

View File

@ -0,0 +1,54 @@
import DS from 'ember-data';
import Converter from 'yarn-ui/utils/converter';
export default DS.JSONAPISerializer.extend({
internalNormalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var fixedPayload = {
id: payload.containerId,
type: primaryModelClass.modelName, // yarn-app
attributes: {
allocatedMB: payload.allocatedMB,
allocatedVCores: payload.allocatedVCores,
assignedNodeId: payload.assignedNodeId,
priority: payload.priority,
startedTime: Converter.timeStampToDate(payload.startedTime),
finishedTime: Converter.timeStampToDate(payload.finishedTime),
elapsedTime: payload.elapsedTime,
logUrl: payload.logUrl,
containerExitStatus: payload.containerExitStatus,
containerState: payload.containerState,
nodeHttpAddress: payload.nodeHttpAddress
}
};
return fixedPayload;
},
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var p = this.internalNormalizeSingleResponse(store,
primaryModelClass, payload, id, requestType);
return { data: p };
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
if (payload && payload.container) {
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = payload.container.map(singleContainer => {
return this.internalNormalizeSingleResponse(store, primaryModelClass,
singleContainer, singleContainer.id, requestType);
}, this);
return normalizedArrayResponse;
}
normalizedArrayResponse.data = [];
return normalizedArrayResponse;
}
});

View File

@ -0,0 +1,127 @@
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
normalizeSingleResponse(store, primaryModelClass, payload, id,
requestType) {
var children = [];
if (payload.queues) {
payload.queues.queue.forEach(function(queue) {
children.push(queue.queueName);
});
}
var includedData = [];
var relationshipUserData = [];
// update user models
if (payload.users && payload.users.user) {
payload.users.user.forEach(function(u) {
includedData.push({
type: "YarnUser",
id: u.username + "_" + payload.queueName,
attributes: {
name: u.username,
queueName: payload.queueName,
usedMemoryMB: u.resourcesUsed.memory || 0,
usedVCore: u.resourcesUsed.vCores || 0,
}
});
relationshipUserData.push({
type: "YarnUser",
id: u.username + "_" + payload.queueName,
})
});
}
var fixedPayload = {
id: id,
type: primaryModelClass.modelName, // yarn-queue
attributes: {
name: payload.queueName,
parent: payload.myParent,
children: children,
capacity: payload.capacity,
usedCapacity: payload.usedCapacity,
maxCapacity: payload.maxCapacity,
absCapacity: payload.absoluteCapacity,
absMaxCapacity: payload.absoluteMaxCapacity,
absUsedCapacity: payload.absoluteUsedCapacity,
state: payload.state,
userLimit: payload.userLimit,
userLimitFactor: payload.userLimitFactor,
preemptionDisabled: payload.preemptionDisabled,
numPendingApplications: payload.numPendingApplications,
numActiveApplications: payload.numActiveApplications,
},
// Relationships
relationships: {
users: {
data: relationshipUserData
}
}
};
return {
queue: this._super(store, primaryModelClass, fixedPayload, id, requestType),
includedData: includedData
}
},
handleQueue(store, primaryModelClass, payload, id, requestType) {
var data = [];
var includedData = []
var result = this.normalizeSingleResponse(store, primaryModelClass,
payload, id, requestType);
data.push(result.queue);
includedData = includedData.concat(result.includedData);
if (payload.queues) {
for (var i = 0; i < payload.queues.queue.length; i++) {
var queue = payload.queues.queue[i];
queue.myParent = payload.queueName;
var childResult = this.handleQueue(store, primaryModelClass, queue,
queue.queueName,
requestType);
data = data.concat(childResult.data);
includedData = includedData.concat(childResult.includedData);
}
}
return {
data: data,
includedData, includedData
}
},
normalizeArrayResponse(store, primaryModelClass, payload, id,
requestType) {
var normalizedArrayResponse = {};
var result = this.handleQueue(store,
primaryModelClass,
payload.scheduler.schedulerInfo, "root", requestType);
normalizedArrayResponse.data = result.data;
normalizedArrayResponse.included = result.includedData;
console.log(normalizedArrayResponse);
return normalizedArrayResponse;
/*
// return expected is { data: [ {}, {} ] }
var normalizedArrayResponse = {};
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = payload.apps.app.map(singleApp => {
return this.normalizeSingleResponse(store, primaryModelClass, singleApp, singleApp.id, requestType);
}, this);
return normalizedArrayResponse;
*/
}
});

View File

@ -0,0 +1,141 @@
/*
Over all style
*/
text {
font: 12px sans-serif;
}
text.small {
font: 8px sans-serif;
}
html, body
{
margin: 0px;
padding: 0px;
height: 100%;
width: 100%;
}
/*
queue's style (left banner of queues)
*/
text.queue {
font-family : sans-serif;
font-size : 15px;
fill : gray;
}
path.queue {
stroke: gray;
fill: none;
}
circle.queue {
r: 10;
fill: Steelblue;
}
/*
background style
*/
line.grid {
stroke: WhiteSmoke;
}
line.chart {
stroke: Gray;
}
/*
charts styles
*/
text.chart-title {
font-size: 30px;
font-family: sans-serif;
text-anchor: middle;
fill: Gray;
}
text.donut-highlight-text {
font-size: 20px;
font-family: sans-serif;
text-anchor: middle;
fill: Gray;
vertical-align: middle;
}
rect.chart-frame {
fill: none;
stroke: gray;
stroke-dasharray: 10,10;
}
line.chart-leftbanner {
stroke-width: 2;
stroke: gray;
stroke-dasharray: 10,10;
}
text.bar-chart-text {
font-size: 8px;
font-family: sans-serif;
vertical-align: middle;
fill: Gray;;
}
div.tooltip {
position: absolute;
text-align: center;
/*height: 28px;*/
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
/*
* Data table
*/
table.dataTable thead .sorting {
background-image: url("/assets/images/datatables/sort_both.png");
}
table.dataTable thead .sorting_asc {
background-image: url("/assets/images/datatables/sort_asc.png");
}
table.dataTable thead .sorting_desc {
background-image: url("/assets/images/datatables/sort_desc.png");
}
table.dataTable thead .sorting_asc_disabled {
background-image: url("/assets/images/datatables/sort_asc_disabled.png");
}
table.dataTable thead .sorting_desc_disabled {
background-image: url("/assets/images/datatables/sort_desc_disabled.png");
}
/*
* Queue selector
*/
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}

View File

@ -0,0 +1,25 @@
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Apache Hadoop YARN</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="yarnQueue/root">Queues<span class="sr-only">(current)</span></a></li>
<li class="active"><a href="yarnApps">Applications<span class="sr-only">(current)</span></a></li>
<li class="active"><a href="clusterOverview">Cluster Overview<span class="sr-only">(current)</span></a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{{outlet}}

View File

@ -0,0 +1,56 @@
<div class="row">
<div class="col-lg-3 container-fluid" id="finishedapps-donut-chart">
{{donut-chart data=model.firstObject.getFinishedAppsDataForDonutChart
title="Finished Apps"
showLabels=true
parentId="finishedapps-donut-chart"
ratio=0.55
maxHeight=350}}
</div>
<div class="col-lg-3 container-fluid" id="runningapps-donut-chart">
{{donut-chart data=model.firstObject.getRunningAppsDataForDonutChart
title="Running Apps"
showLabels=true
parentId="runningapps-donut-chart"
ratio=0.55
maxHeight=350}}
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-3 container-fluid" id="nodes-donut-chart">
{{donut-chart data=model.firstObject.getNodesDataForDonutChart
title="Node Managers"
showLabels=true
parentId="nodes-donut-chart"
ratio=0.55
maxHeight=350}}
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-3 container-fluid" id="mem-donut-chart">
{{donut-chart data=model.firstObject.getMemoryDataForDonutChart
title="Resource - Memory"
showLabels=true
parentId="mem-donut-chart"
ratio=0.55
maxHeight=350}}
</div>
<div class="col-lg-3 container-fluid" id="vcore-donut-chart">
{{donut-chart data=model.firstObject.getVCoreDataForDonutChart
title="Resource - VCores"
showLabels=true
parentId="vcore-donut-chart"
ratio=0.6
maxHeight=350}}
</div>
</div>
{{outlet}}

View File

@ -0,0 +1,28 @@
<table id="app-attempt-table" class="table table-striped table-bordered" cellspacing="0" width="100%" height="100%">
<tbody>
<tr>
<td>Application Attempt Id</td>
<td>{{attempt.id}}</td>
</tr>
<tr>
<td>Start Time</td>
<td>{{attempt.startTime}}</td>
</tr>
<tr>
<td>AM Container Id</td>
<td>{{attempt.containerId}}</td>
</tr>
<tr>
<td>AM Node Web UI</td>
<td><a href={{attempt.nodeHttpAddress}}>{{attempt.nodeHttpAddress}}</a></td>
</tr>
<tr>
<td>AM Node Id</td>
<td>{{attempt.nodeId}}</td>
</tr>
<tr>
<td>Log</td>
<td><a href={{attempt.logsLink}}>link</a></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,62 @@
<table id={{table-id}} class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Application ID</th>
<th>Name</th>
<th>User</th>
<th>Queue</th>
<th>State</th>
<th>Final Status</th>
<th>Start Time</th>
<th>Elapsed Time</th> <!-- idx = 7 -->
<th>Finished Time</th>
<th>Priority</th>
<th>Progress</th>
</tr>
</thead>
<tbody>
{{#if arr}}
{{#each arr as |app|}}
<tr>
<td><a href="yarnApp/{{app.id}}">{{app.id}}</a></td>
<td>{{app.appName}}</td>
<td>{{app.user}}</td>
<td>{{app.queue}}</td>
<td>{{app.state}}</td>
<td><span class={{app.finalStatusStyle}}>{{app.finalStatus}}</span></td>
<td>{{app.startTime}}</td>
<td>{{app.elapsedTime}}</td>
<td>{{app.finishedTime}}</td>
<td>{{app.priority}}</td>
<td>
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style={{app.progressStyle}}>
{{app.progress}}%
</div>
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td><a href="yarnApp/{{app.id}}">{{app.id}}</a></td>
<td>{{app.appName}}</td>
<td>{{app.user}}</td>
<td>{{app.queue}}</td>
<td>{{app.state}}</td>
<td><span class={{app.finalStatusStyle}}>{{app.finalStatus}}</span></td>
<td>{{app.startTime}}</td>
<td>{{app.elapsedTime}}</td>
<td>{{app.finishedTime}}</td>
<td>{{app.priority}}</td>
<td>
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style={{app.progressStyle}}>
{{app.progress}}%
</div>
</div>
</td>
</tr>
{{/if}}
</tbody>
</table>

View File

@ -0,0 +1,36 @@
<table id="container-table" class="table table-striped table-bordered" cellspacing="0" width="100%" height="100%">
<tbody>
<tr>
<td>Start Time</td>
<td>{{container.startedTime}}</td>
</tr>
<tr>
<td>Finished Time</td>
<td>{{container.finishedTime}}</td>
</tr>
<tr>
<td>Elapsed Time</td>
<td>{{container.elapsedTime}}</td>
</tr>
<tr>
<td>Priority</td>
<td>{{container.priority}}</td>
</tr>
<tr>
<td>Log</td>
<td><a href={{container.logUrl}}>link</a></td>
</tr>
<tr>
<td>Exit Status</td>
<td>{{container.containerExitStatus}}</td>
</tr>
<tr>
<td>State</td>
<td>{{container.containerState}}</td>
</tr>
<tr>
<td>NodeManager UI</td>
<td>{{container.nodeHttpAddress}}</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,40 @@
<table id="queue-configuration-table" class="table table-striped table-bordered" cellspacing="0" width="100%" height="100%">
<thead>
<tr>
<td><b>Configurations</b></td>
<td>Value</td>
</tr>
</thead>
<tbody>
<tr>
<td>Queue Name</td>
<td>{{queue.id}}</td>
</tr>
<tr>
<td>Configured Capacity</td>
<td>{{queue.capacity}}</td>
</tr>
<tr>
<td>Configured Max Capacity</td>
<td>{{queue.maxCapacity}}</td>
</tr>
<tr>
<td>State</td>
<td>{{queue.state}}</td>
</tr>
{{#if queue.isLeafQueue}}
<tr>
<td>User Limit Percent</td>
<td>{{queue.userLimit}}</td>
</tr>
<tr>
<td>User Limit Factor</td>
<td>{{queue.userLimitFactor}}</td>
</tr>
<tr>
<td>Preemption Disabled</td>
<td>{{queue.preemptionDisabled}}</td>
</tr>
{{/if}}
</tbody>
</table>

View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-lg-4">
<select class="js-example-basic-single" width="100%" id="queue-name-selector">
{{item-selector element-id="queue-name-selector" prefix="Queue : " model=model}}
</select>
</div>
</div><!-- /.row -->
<!-- queue selector -->
<div class="row">
<div class="col-md-12 container-fluid" id="tree-selector-container">
{{tree-selector model=model parentId="tree-selector-container" selected=selected}}
</div>
</div>
<hr>
{{outlet}}

View File

@ -0,0 +1,35 @@
<div class="col-md-12 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
{{#if attemptModel}}
Application Attempts
{{else}}
Containers
{{/if}}
</div>
<div class="panel-body">
<div class="col-md-8 container-fluid" id={{parent-id}}>
</div>
<!-- diag info -->
<div class="col-md-4 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
{{#if selected.link}}
<a href={{selected.link}}>{{selected.id}}</a>
{{else}}
{{selected.id}}
{{/if}}
</div>
{{#if attemptModel}}
{{app-attempt-table attempt=selected}}
{{else}}
{{container-table container=selected}}
{{/if}}
</div>
</div>
</div>
</div>
</div>
{{outlet}}

View File

@ -0,0 +1,12 @@
<div class="container-fluid">
<div class="row">
{{app-attempt-table attempt=model.attempt}}
</div>
<!-- containers table -->
<div class="row">
{{timeline-view parent-id="containers-timeline-div" my-id="timeline-view" height="400" rmModel=model.rmContainers tsModel=model.tsContainers label="shortAppAttemptId" attemptModel=false}}
</div>
</div>
{{outlet}}

View File

@ -0,0 +1,145 @@
<div class="container-fluid">
<!-- app table -->
<div class="row">
<div class="col-md-12 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
Application Basic Information
</div>
{{app-table table-id="app-table" app=model.app}}
</div>
</div>
</div>
<!-- diag info and other infos -->
<div class="row">
<!-- diag info -->
<div class="col-md-4 container-fluid">
{{#if model.app.isFailed}}
<div class="panel panel-danger">
<div class="panel-heading">
Diagnostics
</div>
<div class="panel-body">{{model.app.diagnostics}}</div>
</div>
{{else}}
<div class="panel panel-default">
<div class="panel-body">
Diagnostics
</div>
<div class="panel-footer">{{model.app.diagnostics}}</div>
</div>
{{/if}}
</div>
<div class="col-md-5 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">Scheduling Info</div>
<table class="table">
<tbody>
<tr>
<td>Allocated Resource</td>
<td>{{model.app.allocatedResource}}</td>
</tr>
<tr>
<td>Running Containers</td>
<td>{{model.app.runningContainers}}</td>
</tr>
<tr>
<td>Preempted Resource</td>
<td>{{model.app.preemptedResource}}</td>
</tr>
<tr>
<td>Num Non-AM container preempted</td>
<td>{{model.app.numAMContainerPreempted}}</td>
</tr>
<tr>
<td>Num AM container preempted</td>
<td>{{model.app.numAMContainerPreempted}}</td>
</tr>
<tr>
<td>Aggregated Resource Usage</td>
<td>{{model.app.aggregatedResourceUsage}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- other info -->
<div class="col-md-3 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">Other Info</div>
<table class="table">
<tbody>
<tr>
<td>AM Container Log</td>
<td><a href={{model.app.amContainerLogs}}>Link</a></td>
</tr>
<tr>
<td>AM Host Http Addr</td>
<td><a href={{model.app.amHostHttpAddress}}>Link</a></td>
</tr>
<tr>
<td>Log Aggregation Status</td>
<td>{{model.app.logAggregationStatus}}</td>
</tr>
<tr>
<td>Is Unmanaged AM</td>
<td>{{model.app.unmanagedApplication}}</td>
</tr>
<tr>
<td>AM Node Label Expression</td>
<td>{{model.app.amNodeLabelExpression}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!--
<div class="row">
<div class="col-md-12 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
Application Attempts
</div>
<table id="app-attempt-table" class="table table-striped table-bordered" cellspacing="0" width="100%" height="100%">
<thead>
<tr>
<th>Start Time</th>
<th>Master ContainerId</th>
<th>Node Http Address</th>
<th>Node Id</th>
<th>Logs Link</th>
</tr>
</thead>
<tbody>
{{#each model.attempts as |attempt|}}
<tr>
<td>{{attempt.startTime}}</td>
<td>{{attempt.containerId}}</td>
<td><a href={{attempt.nodeHttpAddress}}>{{attempt.nodeHttpAddress}}</a></td>
<td>{{attempt.nodeId}}</td>
<td><a href={{attempt.logsLink}}>link</a></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
-->
<!-- timeline view of children -->
<div class="row">
{{timeline-view parent-id="attempt-timeline-div" my-id="timeline-view" height="100%" rmModel=model.attempts label="shortAppAttemptId" attemptModel=true}}
</div>
</div>
<!--
{{simple-table table-id="app-attempt-table" paging=false bFilter=false}}
-->
{{outlet}}

View File

@ -0,0 +1,3 @@
{{app-table table-id="apps-table" arr=model}}
{{simple-table table-id="apps-table" bFilter=true colTypes="elapsed-time" colTargets="7"}}
{{outlet}}

View File

@ -0,0 +1,48 @@
<div class="container-fluid">
{{queue-navigator model=model.queues selected=model.selected}}
</div>
<div class="row">
<div class="col-lg-3 container-fluid">
{{queue-configuration-table queue=model.selectedQueue}}
</div>
<div class="col-lg-3 container-fluid" id="capacity-bar-chart">
{{bar-chart data=model.selectedQueue.capacitiesBarChartData
title="Queue Capacities"
parentId="capacity-bar-chart"
textWidth=150
ratio=0.5
maxHeight=350}}
</div>
{{#if model.selectedQueue.hasUserUsages}}
<div class="col-lg-3 container-fluid" id="userusage-donut-chart">
{{donut-chart data=model.selectedQueue.userUsagesDonutChartData
title="User Usages"
showLabels=true
parentId="userusage-donut-chart"
maxHeight=350}}
</div>
{{/if}}
<div class="col-lg-3 container-fluid" id="numapplications-donut-chart">
{{donut-chart data=model.selectedQueue.numOfApplicationsDonutChartData
title="Running Apps"
showLabels=true
parentId="numapplications-donut-chart"
ratio=0.5
maxHeight=350}}
</div>
</div>
<hr>
<div class="row">
<div class="col-md-12 container-fluid">
{{app-table table-id="apps-table" arr=model.apps}}
{{simple-table table-id="apps-table" bFilter=true colTypes="elapsed-time" colTargets="7"}}
</div>
</div>
{{outlet}}

View File

@ -0,0 +1,74 @@
export default {
containerIdToAttemptId: function(containerId) {
if (containerId) {
var arr = containerId.split('_');
var attemptId = ["appattempt", arr[1],
arr[2], this.padding(arr[3], 6)];
return attemptId.join('_');
}
},
attemptIdToAppId: function(attemptId) {
if (attemptId) {
var arr = attemptId.split('_');
var appId = ["application", arr[1],
arr[2]].join('_');
return appId;
}
},
padding: function(str, toLen=2) {
str = str.toString();
if (str.length >= toLen) {
return str;
}
return '0'.repeat(toLen - str.length) + str;
},
resourceToString: function(mem, cpu) {
mem = Math.max(0, mem);
cpu = Math.max(0, cpu);
return mem + " MBs, " + cpu + " VCores";
},
msToElapsedTime: function(timeInMs) {
var sec_num = timeInMs / 1000; // don't forget the second param
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
var timeStrArr = [];
if (hours > 0) {
timeStrArr.push(hours + ' Hrs');
}
if (minutes > 0) {
timeStrArr.push(minutes + ' Mins');
}
if (seconds > 0) {
timeStrArr.push(Math.round(seconds) + " Secs");
}
return timeStrArr.join(' : ');
},
elapsedTimeToMs: function(elapsedTime) {
elapsedTime = elapsedTime.toLowerCase();
var arr = elapsedTime.split(' : ');
var total = 0;
for (var i = 0; i < arr.length; i++) {
if (arr[i].indexOf('hr') > 0) {
total += parseInt(arr[i].substring(0, arr[i].indexOf(' '))) * 3600;
} else if (arr[i].indexOf('min') > 0) {
total += parseInt(arr[i].substring(0, arr[i].indexOf(' '))) * 60;
} else if (arr[i].indexOf('sec') > 0) {
total += parseInt(arr[i].substring(0, arr[i].indexOf(' ')));
}
}
return total * 1000;
},
timeStampToDate: function(timeStamp) {
var dateTimeString = moment(parseInt(timeStamp)).format("YYYY/MM/DD HH:mm:ss");
return dateTimeString;
},
dateToTimeStamp: function(date) {
if (date) {
var ts = moment(date, "YYYY/MM/DD HH:mm:ss").valueOf();
return ts;
}
}
}

View File

@ -0,0 +1,15 @@
import Converter from 'yarn-ui/utils/converter';
export default {
_initElapsedTimeSorter: function() {
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
"elapsed-time-pre": function (a) {
return Converter.padding(Converter.elapsedTimeToMs(a), 20);
},
});
},
initDataTableSorter: function() {
this._initElapsedTimeSorter();
},
}

View File

@ -0,0 +1,22 @@
{
"name": "yarn-ui",
"dependencies": {
"ember": "2.0.1",
"ember-cli-shims": "ember-cli/ember-cli-shims#0.0.4",
"ember-cli-test-loader": "ember-cli-test-loader#0.1.3",
"ember-data": "2.0.0",
"ember-load-initializers": "ember-cli/ember-load-initializers#0.1.6",
"ember-qunit": "0.4.9",
"ember-qunit-notifications": "0.0.7",
"ember-resolver": "~0.1.18",
"jquery": "^1.11.3",
"loader.js": "ember-cli/loader.js#3.2.1",
"qunit": "~1.18.0",
"bootstrap": "~3.3.2",
"d3": "~3.5.6",
"datatables": "~1.10.8",
"spin.js": "~2.3.2",
"momentjs": "~2.10.6",
"select2": "4.0.0"
}
}

View File

@ -0,0 +1,47 @@
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'yarn-ui',
environment: environment,
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
// ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.baseURL = '/';
ENV.locationType = 'none';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
}
if (environment === 'production') {
}
return ENV;
};

View File

@ -0,0 +1,29 @@
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
var app = new EmberApp(defaults, {
// Add options here
});
app.import("bower_components/datatables/media/css/jquery.dataTables.min.css");
app.import("bower_components/datatables/media/js/jquery.dataTables.min.js");
app.import("bower_components/momentjs/min/moment.min.js");
app.import("bower_components/select2/dist/css/select2.min.css");
app.import("bower_components/select2/dist/js/select2.min.js");
// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.
return app.toTree();
};

View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs"
}
}

View File

@ -0,0 +1,44 @@
{
"name": "yarn-ui",
"version": "0.0.0",
"description": "Small description for yarn-ui goes here",
"private": true,
"directories": {
"doc": "doc",
"test": "tests"
},
"scripts": {
"build": "ember build",
"start": "ember server",
"test": "ember test"
},
"repository": "",
"engines": {
"node": ">= 0.10.0"
},
"author": "",
"license": "MIT",
"devDependencies": {
"broccoli-asset-rev": "^2.1.2",
"ember-bootstrap": "0.2.0",
"ember-cli": "1.13.8",
"ember-cli-app-version": "0.5.0",
"ember-cli-babel": "^5.1.3",
"ember-cli-content-security-policy": "0.4.0",
"ember-cli-dependency-checker": "^1.0.1",
"ember-cli-htmlbars": "0.7.9",
"ember-cli-htmlbars-inline-precompile": "^0.2.0",
"ember-cli-ic-ajax": "0.2.1",
"ember-cli-inject-live-reload": "^1.3.1",
"ember-cli-qunit": "^1.0.0",
"ember-cli-release": "0.2.3",
"ember-cli-sri": "^1.0.3",
"ember-cli-uglify": "^1.2.0",
"ember-d3": "0.1.0",
"ember-data": "1.13.8",
"ember-disable-proxy-controllers": "^1.0.0",
"ember-export-application-global": "^1.0.3",
"ember-spin-spinner": "0.2.3",
"select2": "4.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
<!-- Most restrictive policy: -->
<site-control permitted-cross-domain-policies="none"/>
<!-- Least restrictive policy: -->
<!--
<site-control permitted-cross-domain-policies="all"/>
<allow-access-from domain="*" to-ports="*" secure="false"/>
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
-->
</cross-domain-policy>

View File

@ -0,0 +1,3 @@
# http://www.robotstxt.org
User-agent: *
Disallow:

View File

@ -0,0 +1,12 @@
{
"framework": "qunit",
"test_page": "tests/index.html?hidepassed",
"disable_watching": true,
"launch_in_ci": [
"PhantomJS"
],
"launch_in_dev": [
"PhantomJS",
"Chrome"
]
}

View File

@ -0,0 +1,52 @@
{
"predef": [
"document",
"window",
"location",
"setTimeout",
"$",
"-Promise",
"define",
"console",
"visit",
"exists",
"fillIn",
"click",
"keyEvent",
"triggerEvent",
"find",
"findWithAssert",
"wait",
"DS",
"andThen",
"currentURL",
"currentPath",
"currentRouteName"
],
"node": false,
"browser": false,
"boss": true,
"curly": true,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"esnext": true,
"unused": true
}

View File

@ -0,0 +1,11 @@
import Resolver from 'ember/resolver';
import config from '../../config/environment';
var resolver = Resolver.create();
resolver.namespace = {
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix
};
export default resolver;

View File

@ -0,0 +1,18 @@
import Ember from 'ember';
import Application from '../../app';
import config from '../../config/environment';
export default function startApp(attrs) {
var application;
var attributes = Ember.merge({}, config.APP);
attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
Ember.run(function() {
application = Application.create(attributes);
application.setupForTesting();
application.injectTestHelpers();
});
return application;
}

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>YarnUi Tests</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for 'head'}}
{{content-for 'test-head'}}
<link rel="stylesheet" href="assets/vendor.css">
<link rel="stylesheet" href="assets/yarn-ui.css">
<link rel="stylesheet" href="assets/test-support.css">
{{content-for 'head-footer'}}
{{content-for 'test-head-footer'}}
</head>
<body>
{{content-for 'body'}}
{{content-for 'test-body'}}
<script src="assets/vendor.js"></script>
<script src="assets/test-support.js"></script>
<script src="assets/yarn-ui.js"></script>
<script src="testem.js"></script>
<script src="assets/test-loader.js"></script>
{{content-for 'body-footer'}}
{{content-for 'test-body-footer'}}
</body>
</html>

View File

@ -0,0 +1,6 @@
import resolver from './helpers/resolver';
import {
setResolver
} from 'ember-qunit';
setResolver(resolver);

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('adapter:yarn-app', 'Unit | Adapter | yarn app', {
// Specify the other units that are required for this test.
// needs: ['serializer:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
var adapter = this.subject();
assert.ok(adapter);
});

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:yarn-apps', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(controller);
});

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:yarn-queues', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(controller);
});

View File

@ -0,0 +1,12 @@
import Ember from 'ember';
import ChartsMixin from '../../../mixins/charts';
import { module, test } from 'qunit';
module('Unit | Mixin | charts');
// Replace this with your real tests.
test('it works', function(assert) {
var ChartsObject = Ember.Object.extend(ChartsMixin);
var subject = ChartsObject.create();
assert.ok(subject);
});

View File

@ -0,0 +1,12 @@
import { moduleForModel, test } from 'ember-qunit';
moduleForModel('yarn-app', 'Unit | Model | yarn app', {
// Specify the other units that are required for this test.
needs: []
});
test('it exists', function(assert) {
var model = this.subject();
// var store = this.store();
assert.ok(!!model);
});

Some files were not shown because too many files have changed in this diff Show More