mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Allow adding metric charts to home page
This commit is contained in:
parent
10c70c8285
commit
f4546d0888
@ -176,6 +176,19 @@ var Swirl;
|
||||
}
|
||||
}
|
||||
Core.Tab = Tab;
|
||||
class FilterBox {
|
||||
constructor(elem, callback, timeout) {
|
||||
this.$elem = $(elem);
|
||||
this.$elem.keyup(() => {
|
||||
if (this.timer > 0) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
let text = this.$elem.val().toLowerCase();
|
||||
this.timer = setTimeout(() => callback(text), timeout || 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
Core.FilterBox = FilterBox;
|
||||
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
/*!
|
||||
@ -1104,6 +1117,461 @@ var Swirl;
|
||||
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Core;
|
||||
(function (Core) {
|
||||
class GraphOptions {
|
||||
constructor() {
|
||||
this.type = "line";
|
||||
this.width = 12;
|
||||
this.height = 150;
|
||||
}
|
||||
}
|
||||
Core.GraphOptions = GraphOptions;
|
||||
class Graph {
|
||||
constructor(elem, opts) {
|
||||
this.$elem = $(elem);
|
||||
this.opts = $.extend(new GraphOptions(), opts);
|
||||
this.name = this.$elem.data("chart-name");
|
||||
}
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
getElem() {
|
||||
return this.$elem;
|
||||
}
|
||||
getOptions() {
|
||||
return this.opts;
|
||||
}
|
||||
}
|
||||
Core.Graph = Graph;
|
||||
class ValueGraph extends Graph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
}
|
||||
setData(d) {
|
||||
}
|
||||
resize(w, h) {
|
||||
}
|
||||
}
|
||||
Core.ValueGraph = ValueGraph;
|
||||
class ComplexGraph extends Graph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
if (!this.opts.colors) {
|
||||
this.opts.colors = ComplexGraph.defaultColors;
|
||||
}
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
responsive: false,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
}
|
||||
};
|
||||
this.fillConfig();
|
||||
this.ctx = $(elem).find("canvas").get(0).getContext('2d');
|
||||
if (opts.height) {
|
||||
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
|
||||
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
|
||||
}
|
||||
this.chart = new Chart(this.ctx, this.config);
|
||||
}
|
||||
setData(d) {
|
||||
}
|
||||
resize(w, h) {
|
||||
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
|
||||
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
|
||||
this.chart.resize();
|
||||
}
|
||||
fillConfig() {
|
||||
}
|
||||
static formatValue(value, unit) {
|
||||
switch (unit) {
|
||||
case "percent:100":
|
||||
return value.toFixed(1) + "%";
|
||||
case "percent:1":
|
||||
return (value * 100).toFixed(1) + "%";
|
||||
case "time:s":
|
||||
if (value < 1) {
|
||||
return (value * 1000).toFixed(0) + 'ms';
|
||||
}
|
||||
else {
|
||||
return value.toFixed(2) + 's';
|
||||
}
|
||||
case "size:bytes":
|
||||
if (value < 1024) {
|
||||
return value.toString() + 'B';
|
||||
}
|
||||
else if (value < 1048576) {
|
||||
return (value / 1024).toFixed(2) + 'K';
|
||||
}
|
||||
else if (value < 1073741824) {
|
||||
return (value / 1048576).toFixed(2) + 'M';
|
||||
}
|
||||
else {
|
||||
return (value / 1073741824).toFixed(2) + 'G';
|
||||
}
|
||||
default:
|
||||
return value.toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
ComplexGraph.defaultColors = [
|
||||
'rgb(255, 99, 132)',
|
||||
'rgb(75, 192, 192)',
|
||||
'rgb(255, 159, 64)',
|
||||
'rgb(54, 162, 235)',
|
||||
'rgb(153, 102, 255)',
|
||||
'rgb(255, 205, 86)',
|
||||
'rgb(201, 203, 207)',
|
||||
];
|
||||
Core.ComplexGraph = ComplexGraph;
|
||||
class VectorGraph extends ComplexGraph {
|
||||
fillConfig() {
|
||||
this.config.options.legend = {
|
||||
position: "right"
|
||||
};
|
||||
this.config.options.tooltips = {
|
||||
callbacks: {
|
||||
label: (tooltipItem, data) => {
|
||||
let label = data.labels[tooltipItem.index] + ": ";
|
||||
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
||||
if (typeof p == "number") {
|
||||
label += ComplexGraph.formatValue(p, this.opts.unit);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
setData(d) {
|
||||
this.config.data.datasets = [{
|
||||
data: d.data,
|
||||
backgroundColor: this.opts.colors,
|
||||
}];
|
||||
this.config.data.labels = d.labels;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
Core.VectorGraph = VectorGraph;
|
||||
class MatrixGraph extends ComplexGraph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
}
|
||||
fillConfig() {
|
||||
this.config.options.scales = {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
};
|
||||
if (this.opts.unit) {
|
||||
this.config.options.scales.yAxes = [{
|
||||
ticks: {
|
||||
callback: (n) => ComplexGraph.formatValue(n, this.opts.unit),
|
||||
}
|
||||
}];
|
||||
this.config.options.tooltips = {
|
||||
callbacks: {
|
||||
label: (tooltipItem, data) => {
|
||||
let label = data.datasets[tooltipItem.datasetIndex].label + ": ";
|
||||
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
||||
label += ComplexGraph.formatValue(p.y, this.opts.unit);
|
||||
return label;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
setData(d) {
|
||||
let datasets = (d);
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.opts.colors.length) ? this.opts.colors[i] : this.opts.colors[0];
|
||||
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
|
||||
ds.borderColor = color;
|
||||
ds.borderWidth = 2;
|
||||
ds.pointRadius = 2;
|
||||
});
|
||||
this.config.data.datasets = d;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
Core.MatrixGraph = MatrixGraph;
|
||||
class GraphFactory {
|
||||
static create(elem) {
|
||||
let $elem = $(elem);
|
||||
let opts = {
|
||||
type: $elem.data("chart-type"),
|
||||
unit: $elem.data("chart-unit"),
|
||||
width: $elem.data("chart-width"),
|
||||
height: $elem.data("chart-height"),
|
||||
colors: $elem.data("chart-colors"),
|
||||
};
|
||||
switch (opts.type) {
|
||||
case "value":
|
||||
return new ValueGraph($elem, opts);
|
||||
case "line":
|
||||
case "bar":
|
||||
return new MatrixGraph($elem, opts);
|
||||
case "pie":
|
||||
return new VectorGraph($elem, opts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Core.GraphFactory = GraphFactory;
|
||||
class GraphPanelOptions {
|
||||
constructor() {
|
||||
this.time = "30m";
|
||||
this.refreshInterval = 15000;
|
||||
}
|
||||
}
|
||||
Core.GraphPanelOptions = GraphPanelOptions;
|
||||
class GraphPanel {
|
||||
constructor(elem, opts) {
|
||||
this.charts = [];
|
||||
this.opts = $.extend(new GraphPanelOptions(), opts);
|
||||
this.$panel = $(elem);
|
||||
this.$panel.children().each((i, e) => {
|
||||
let g = GraphFactory.create(e);
|
||||
if (g != null) {
|
||||
this.charts.push(g);
|
||||
}
|
||||
});
|
||||
$(window).resize(e => {
|
||||
$.each(this.charts, (i, g) => {
|
||||
g.resize(0, 0);
|
||||
});
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
refreshData() {
|
||||
this.loadData();
|
||||
if (this.opts.refreshInterval > 0) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
|
||||
}
|
||||
}
|
||||
refresh() {
|
||||
if (!this.timer) {
|
||||
this.loadData();
|
||||
if (this.opts.refreshInterval > 0) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = 0;
|
||||
}
|
||||
setTime(time) {
|
||||
this.opts.time = time;
|
||||
this.loadData();
|
||||
}
|
||||
addGraph(c) {
|
||||
for (let i = 0; i < this.charts.length; i++) {
|
||||
let chart = this.charts[i];
|
||||
if (chart.getName() === c.name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let $chart = $(`<div class="column is-${c.width}" data-chart-name="${c.name}" data-chart-type="${c.type}" data-chart-unit="${c.unit}" data-chart-width="${c.width}" data-chart-height="${c.height}">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">${c.title}</p>
|
||||
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
|
||||
<span class="icon">
|
||||
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div style="height: ${c.height}px">
|
||||
<canvas id="canvas_${c.name}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
this.$panel.append($chart);
|
||||
let g = GraphFactory.create($chart);
|
||||
if (g != null) {
|
||||
this.charts.push(g);
|
||||
}
|
||||
this.loadData();
|
||||
}
|
||||
removeGraph(name) {
|
||||
let index;
|
||||
for (let i = 0; i < this.charts.length; i++) {
|
||||
let c = this.charts[i];
|
||||
if (c.getName() === name) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.loadData();
|
||||
}
|
||||
save() {
|
||||
let charts = this.charts.map(c => {
|
||||
return {
|
||||
name: c.getName(),
|
||||
width: c.getOptions().width,
|
||||
height: c.getOptions().height,
|
||||
};
|
||||
});
|
||||
let args = {
|
||||
name: this.opts.name,
|
||||
key: this.opts.key || '',
|
||||
charts: charts,
|
||||
};
|
||||
$ajax.post(`/system/chart/save_panel`, args).json((r) => {
|
||||
if (!r.success) {
|
||||
Core.Modal.alert(r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
loadData() {
|
||||
let args = {
|
||||
charts: this.charts.map(c => c.getName()).join(","),
|
||||
time: this.opts.time,
|
||||
};
|
||||
if (this.opts.key) {
|
||||
args.key = this.opts.key;
|
||||
}
|
||||
$ajax.get(`/system/chart/data`, args).json((d) => {
|
||||
$.each(this.charts, (i, g) => {
|
||||
if (d[g.getName()]) {
|
||||
g.setData(d[g.getName()]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Core.GraphPanel = GraphPanel;
|
||||
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Modal = Swirl.Core.Modal;
|
||||
var GraphPanel = Swirl.Core.GraphPanel;
|
||||
var FilterBox = Swirl.Core.FilterBox;
|
||||
class IndexPage {
|
||||
constructor() {
|
||||
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
|
||||
this.panel = new GraphPanel("#div-charts", { name: "home" });
|
||||
$("#btn-add").click(this.showAddDlg.bind(this));
|
||||
$("#btn-add-chart").click(this.addChart.bind(this));
|
||||
$("#btn-save").click(() => {
|
||||
this.panel.save();
|
||||
});
|
||||
}
|
||||
showAddDlg() {
|
||||
let $panel = $("#nav-charts");
|
||||
$panel.find("label.panel-block").remove();
|
||||
$ajax.get(`/system/chart/query`, { dashboard: "home" }).json((charts) => {
|
||||
for (let i = 0; i < charts.length; i++) {
|
||||
let c = charts[i];
|
||||
$panel.append(`<label class="panel-block">
|
||||
<input type="checkbox" value="${c.name}" data-index="${i}">${c.name}: ${c.title}
|
||||
</label>`);
|
||||
}
|
||||
this.charts = charts;
|
||||
this.$charts = $panel.find("label.panel-block");
|
||||
});
|
||||
let dlg = new Modal("#dlg-add-chart");
|
||||
dlg.show();
|
||||
}
|
||||
filterCharts(text) {
|
||||
if (!text) {
|
||||
this.$charts.show();
|
||||
return;
|
||||
}
|
||||
this.$charts.each((i, elem) => {
|
||||
let $elem = $(elem);
|
||||
let texts = [
|
||||
this.charts[i].name.toLowerCase(),
|
||||
this.charts[i].title.toLowerCase(),
|
||||
this.charts[i].desc.toLowerCase(),
|
||||
];
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
let index = texts[i].indexOf(text);
|
||||
if (index >= 0) {
|
||||
$elem.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$elem.hide();
|
||||
});
|
||||
}
|
||||
addChart() {
|
||||
this.$charts.each((i, e) => {
|
||||
if ($(e).find(":checked").length > 0) {
|
||||
let c = this.charts[i];
|
||||
this.panel.addGraph(c);
|
||||
}
|
||||
});
|
||||
Modal.close();
|
||||
}
|
||||
}
|
||||
Swirl.IndexPage = IndexPage;
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Metric;
|
||||
(function (Metric) {
|
||||
var Modal = Swirl.Core.Modal;
|
||||
var Dispatcher = Swirl.Core.Dispatcher;
|
||||
var FilterBox = Swirl.Core.FilterBox;
|
||||
class ListPage {
|
||||
constructor() {
|
||||
this.$charts = $("#div-charts").children();
|
||||
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
|
||||
Dispatcher.bind("#div-charts").on("delete-chart", this.deleteChart.bind(this));
|
||||
}
|
||||
deleteChart(e) {
|
||||
let $container = $(e.target).closest("div.column");
|
||||
let name = $container.data("name");
|
||||
Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", (dlg, e) => {
|
||||
$ajax.post(name + "/delete").trigger(e.target).json(r => {
|
||||
$container.remove();
|
||||
dlg.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
filterCharts(text) {
|
||||
if (!text) {
|
||||
this.$charts.show();
|
||||
return;
|
||||
}
|
||||
this.$charts.each((i, elem) => {
|
||||
let $elem = $(elem), texts = [
|
||||
$elem.data("name").toLowerCase(),
|
||||
$elem.data("title").toLowerCase(),
|
||||
$elem.data("desc").toLowerCase(),
|
||||
];
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
let index = texts[i].indexOf(text);
|
||||
if (index >= 0) {
|
||||
$elem.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$elem.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
Metric.ListPage = ListPage;
|
||||
})(Metric = Swirl.Metric || (Swirl.Metric = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Config;
|
||||
(function (Config) {
|
||||
@ -1857,270 +2325,6 @@ var Swirl;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Core;
|
||||
(function (Core) {
|
||||
class GraphOptions {
|
||||
constructor() {
|
||||
this.type = "line";
|
||||
this.width = 12;
|
||||
this.height = 50;
|
||||
}
|
||||
}
|
||||
Core.GraphOptions = GraphOptions;
|
||||
class Graph {
|
||||
constructor(elem, opts) {
|
||||
this.$elem = $(elem);
|
||||
this.opts = $.extend(new GraphOptions(), opts);
|
||||
this.name = this.$elem.data("chart-name");
|
||||
}
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
Core.Graph = Graph;
|
||||
class ValueGraph extends Graph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
}
|
||||
setData(d) {
|
||||
}
|
||||
setSize(w, h) {
|
||||
}
|
||||
}
|
||||
Core.ValueGraph = ValueGraph;
|
||||
class ComplexGraph extends Graph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
if (!this.opts.colors) {
|
||||
this.opts.colors = ComplexGraph.defaultColors;
|
||||
}
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
}
|
||||
};
|
||||
this.fillConfig();
|
||||
this.ctx = $(elem).find("canvas").get(0).getContext('2d');
|
||||
if (opts.height) {
|
||||
this.ctx.canvas.height = opts.height;
|
||||
}
|
||||
this.chart = new Chart(this.ctx, this.config);
|
||||
}
|
||||
setData(d) {
|
||||
}
|
||||
setSize(w, h) {
|
||||
this.ctx.canvas.height = h;
|
||||
this.chart.update();
|
||||
}
|
||||
fillConfig() {
|
||||
}
|
||||
static formatValue(value, unit) {
|
||||
switch (unit) {
|
||||
case "percent:100":
|
||||
return value.toFixed(1) + "%";
|
||||
case "percent:1":
|
||||
return (value * 100).toFixed(1) + "%";
|
||||
case "time:s":
|
||||
if (value < 1) {
|
||||
return (value * 1000).toFixed(0) + 'ms';
|
||||
}
|
||||
else {
|
||||
return value.toFixed(2) + 's';
|
||||
}
|
||||
case "size:bytes":
|
||||
if (value < 1024) {
|
||||
return value.toString() + 'B';
|
||||
}
|
||||
else if (value < 1048576) {
|
||||
return (value / 1024).toFixed(2) + 'K';
|
||||
}
|
||||
else if (value < 1073741824) {
|
||||
return (value / 1048576).toFixed(2) + 'M';
|
||||
}
|
||||
else {
|
||||
return (value / 1073741824).toFixed(2) + 'G';
|
||||
}
|
||||
default:
|
||||
return value.toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
ComplexGraph.defaultColors = [
|
||||
'rgb(255, 99, 132)',
|
||||
'rgb(75, 192, 192)',
|
||||
'rgb(255, 159, 64)',
|
||||
'rgb(54, 162, 235)',
|
||||
'rgb(153, 102, 255)',
|
||||
'rgb(255, 205, 86)',
|
||||
'rgb(201, 203, 207)',
|
||||
];
|
||||
Core.ComplexGraph = ComplexGraph;
|
||||
class VectorGraph extends ComplexGraph {
|
||||
fillConfig() {
|
||||
this.config.options.legend = {
|
||||
position: "right"
|
||||
};
|
||||
this.config.options.tooltips = {
|
||||
callbacks: {
|
||||
label: (tooltipItem, data) => {
|
||||
let label = data.labels[tooltipItem.index] + ": ";
|
||||
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
||||
if (typeof p == "number") {
|
||||
label += ComplexGraph.formatValue(p, this.opts.unit);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
setData(d) {
|
||||
this.config.data.datasets = [{
|
||||
data: d.data,
|
||||
backgroundColor: this.opts.colors,
|
||||
}];
|
||||
this.config.data.labels = d.labels;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
Core.VectorGraph = VectorGraph;
|
||||
class MatrixGraph extends ComplexGraph {
|
||||
constructor(elem, opts) {
|
||||
super(elem, opts);
|
||||
}
|
||||
fillConfig() {
|
||||
this.config.options.scales = {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
};
|
||||
if (this.opts.unit) {
|
||||
this.config.options.scales.yAxes = [{
|
||||
ticks: {
|
||||
callback: (n) => ComplexGraph.formatValue(n, this.opts.unit),
|
||||
}
|
||||
}];
|
||||
this.config.options.tooltips = {
|
||||
callbacks: {
|
||||
label: (tooltipItem, data) => {
|
||||
let label = data.datasets[tooltipItem.datasetIndex].label + ": ";
|
||||
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
||||
label += ComplexGraph.formatValue(p.y, this.opts.unit);
|
||||
return label;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
setData(d) {
|
||||
let datasets = (d);
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.opts.colors.length) ? this.opts.colors[i] : this.opts.colors[0];
|
||||
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
|
||||
ds.borderColor = color;
|
||||
ds.borderWidth = 2;
|
||||
ds.pointRadius = 2;
|
||||
});
|
||||
this.config.data.datasets = d;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
Core.MatrixGraph = MatrixGraph;
|
||||
class GraphFactory {
|
||||
static create(elem) {
|
||||
let $elem = $(elem);
|
||||
let opts = {
|
||||
type: $elem.data("chart-type"),
|
||||
unit: $elem.data("chart-unit"),
|
||||
height: $elem.data("chart-height"),
|
||||
colors: $elem.data("chart-colors"),
|
||||
};
|
||||
switch (opts.type) {
|
||||
case "value":
|
||||
return new ValueGraph($elem, opts);
|
||||
case "line":
|
||||
case "bar":
|
||||
return new MatrixGraph($elem, opts);
|
||||
case "pie":
|
||||
return new VectorGraph($elem, opts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Core.GraphFactory = GraphFactory;
|
||||
class GraphPanelOptions {
|
||||
constructor() {
|
||||
this.time = "30m";
|
||||
this.refreshInterval = 15000;
|
||||
}
|
||||
}
|
||||
Core.GraphPanelOptions = GraphPanelOptions;
|
||||
class GraphPanel {
|
||||
constructor(elems, opts) {
|
||||
this.charts = [];
|
||||
this.opts = $.extend(new GraphPanelOptions(), opts);
|
||||
$(elems).each((i, e) => {
|
||||
let g = GraphFactory.create(e);
|
||||
if (g != null) {
|
||||
this.charts.push(g);
|
||||
}
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
refreshData() {
|
||||
this.loadData();
|
||||
if (this.opts.refreshInterval > 0) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
|
||||
}
|
||||
}
|
||||
refresh() {
|
||||
if (!this.timer) {
|
||||
this.loadData();
|
||||
if (this.opts.refreshInterval > 0) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = 0;
|
||||
}
|
||||
setTime(time) {
|
||||
this.opts.time = time;
|
||||
this.loadData();
|
||||
}
|
||||
loadData() {
|
||||
let args = {
|
||||
dashboard: this.opts.name,
|
||||
time: this.opts.time,
|
||||
};
|
||||
if (this.opts.id) {
|
||||
args.id = this.opts.id;
|
||||
}
|
||||
$ajax.get(`/system/chart/data`, args).json((d) => {
|
||||
$.each(this.charts, (i, g) => {
|
||||
if (d[g.getName()]) {
|
||||
g.setData(d[g.getName()]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Core.GraphPanel = GraphPanel;
|
||||
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Service;
|
||||
(function (Service) {
|
||||
@ -2132,9 +2336,9 @@ var Swirl;
|
||||
if ($cb_time.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.panel = new GraphPanel($("#div-charts").children("div"), {
|
||||
this.panel = new GraphPanel("#div-charts", {
|
||||
name: "service",
|
||||
id: $("#h2-service-name").text()
|
||||
key: $("#h2-service-name").text()
|
||||
});
|
||||
$("#btn-add").click(() => {
|
||||
Modal.alert("Coming soon...");
|
||||
@ -2454,77 +2658,4 @@ var Swirl;
|
||||
Volume.NewPage = NewPage;
|
||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Modal = Swirl.Core.Modal;
|
||||
var GraphPanel = Swirl.Core.GraphPanel;
|
||||
class IndexPage {
|
||||
constructor() {
|
||||
this.panel = new GraphPanel($("#div-charts").children("div"), { name: "home" });
|
||||
$("#btn-add").click(() => {
|
||||
Modal.alert("Coming soon...");
|
||||
});
|
||||
}
|
||||
}
|
||||
Swirl.IndexPage = IndexPage;
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Metric;
|
||||
(function (Metric) {
|
||||
var Modal = Swirl.Core.Modal;
|
||||
var Dispatcher = Swirl.Core.Dispatcher;
|
||||
class FilterBox {
|
||||
constructor(elem, callback, timeout) {
|
||||
this.$elem = $(elem);
|
||||
this.$elem.keyup(() => {
|
||||
if (this.timer > 0) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
let text = this.$elem.val().toLowerCase();
|
||||
this.timer = setTimeout(() => callback(text), timeout || 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
class ListPage {
|
||||
constructor() {
|
||||
this.$charts = $("#div-charts").children();
|
||||
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
|
||||
Dispatcher.bind("#div-charts").on("delete-chart", this.deleteChart.bind(this));
|
||||
}
|
||||
deleteChart(e) {
|
||||
let $container = $(e.target).closest("div.column");
|
||||
let name = $container.data("name");
|
||||
Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", (dlg, e) => {
|
||||
$ajax.post(name + "/delete").trigger(e.target).json(r => {
|
||||
$container.remove();
|
||||
dlg.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
filterCharts(text) {
|
||||
if (!text) {
|
||||
this.$charts.show();
|
||||
return;
|
||||
}
|
||||
this.$charts.each((i, elem) => {
|
||||
let $elem = $(elem), texts = [
|
||||
$elem.data("name").toLowerCase(),
|
||||
$elem.data("title").toLowerCase(),
|
||||
$elem.data("desc").toLowerCase(),
|
||||
];
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
let index = texts[i].indexOf(text);
|
||||
if (index >= 0) {
|
||||
$(elem).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$(elem).hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
Metric.ListPage = ListPage;
|
||||
})(Metric = Swirl.Metric || (Swirl.Metric = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
@ -3,22 +3,7 @@ namespace Swirl.Metric {
|
||||
import Modal = Swirl.Core.Modal;
|
||||
import AjaxResult = Swirl.Core.AjaxResult;
|
||||
import Dispatcher = Swirl.Core.Dispatcher;
|
||||
|
||||
class FilterBox {
|
||||
private $elem: JQuery;
|
||||
private timer: number;
|
||||
|
||||
constructor(elem: string | Element | JQuery, callback: (text: string) => void, timeout?: number) {
|
||||
this.$elem = $(elem);
|
||||
this.$elem.keyup(() => {
|
||||
if (this.timer > 0) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
let text: string = this.$elem.val().toLowerCase();
|
||||
this.timer = setTimeout(() => callback(text), timeout || 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
import FilterBox = Swirl.Core.FilterBox;
|
||||
|
||||
export class ListPage {
|
||||
private fb: FilterBox;
|
||||
@ -59,11 +44,11 @@ namespace Swirl.Metric {
|
||||
for (let i = 0; i<texts.length; i++) {
|
||||
let index = texts[i].indexOf(text);
|
||||
if (index >= 0) {
|
||||
$(elem).show();
|
||||
$elem.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$(elem).hide();
|
||||
$elem.hide();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -208,4 +208,20 @@ namespace Swirl.Core {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FilterBox {
|
||||
private $elem: JQuery;
|
||||
private timer: number;
|
||||
|
||||
constructor(elem: string | Element | JQuery, callback: (text: string) => void, timeout?: number) {
|
||||
this.$elem = $(elem);
|
||||
this.$elem.keyup(() => {
|
||||
if (this.timer > 0) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
let text: string = this.$elem.val().toLowerCase();
|
||||
this.timer = setTimeout(() => callback(text), timeout || 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ namespace Swirl.Core {
|
||||
type?: string = "line";
|
||||
unit?: string;
|
||||
width?: number = 12;
|
||||
height?: number = 50;
|
||||
height?: number = 150;
|
||||
colors?: string[];
|
||||
}
|
||||
|
||||
@ -22,7 +22,15 @@ namespace Swirl.Core {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
abstract setSize(w: number, h: number): void;
|
||||
getElem(): JQuery {
|
||||
return this.$elem;
|
||||
}
|
||||
|
||||
getOptions(): GraphOptions {
|
||||
return this.opts;
|
||||
}
|
||||
|
||||
abstract resize(w: number, h: number): void;
|
||||
|
||||
abstract setData(d: any): void;
|
||||
}
|
||||
@ -40,7 +48,7 @@ namespace Swirl.Core {
|
||||
setData(d: any): void {
|
||||
}
|
||||
|
||||
setSize(w: number, h: number): void {
|
||||
resize(w: number, h: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +77,8 @@ namespace Swirl.Core {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
responsive: false,
|
||||
maintainAspectRatio: false,
|
||||
// title: {
|
||||
// // display: true,
|
||||
// text: opts.title || 'NONE'
|
||||
@ -106,7 +116,8 @@ namespace Swirl.Core {
|
||||
|
||||
this.ctx = (<HTMLCanvasElement>$(elem).find("canvas").get(0)).getContext('2d');
|
||||
if (opts.height) {
|
||||
this.ctx.canvas.height = opts.height;
|
||||
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
|
||||
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
|
||||
}
|
||||
this.chart = new Chart(this.ctx, this.config);
|
||||
}
|
||||
@ -114,9 +125,12 @@ namespace Swirl.Core {
|
||||
setData(d: any): void {
|
||||
}
|
||||
|
||||
setSize(w: number, h: number): void {
|
||||
this.ctx.canvas.height = h;
|
||||
this.chart.update();
|
||||
resize(w: number, h: number): void {
|
||||
// this.ctx.canvas.style.width = this.ctx.canvas.parentElement.offsetWidth + "px";
|
||||
// this.ctx.canvas.style.height = this.ctx.canvas.parentElement.offsetWidth + "px";
|
||||
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
|
||||
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
|
||||
this.chart.resize();
|
||||
}
|
||||
|
||||
protected fillConfig() {
|
||||
@ -266,6 +280,7 @@ namespace Swirl.Core {
|
||||
let opts: GraphOptions = {
|
||||
type: $elem.data("chart-type"),
|
||||
unit: $elem.data("chart-unit"),
|
||||
width: $elem.data("chart-width"),
|
||||
height: $elem.data("chart-height"),
|
||||
colors: $elem.data("chart-colors"),
|
||||
};
|
||||
@ -284,26 +299,33 @@ namespace Swirl.Core {
|
||||
|
||||
export class GraphPanelOptions {
|
||||
name: string;
|
||||
id?: string;
|
||||
key?: string;
|
||||
time?: string = "30m";
|
||||
refreshInterval?: number = 15000; // ms
|
||||
}
|
||||
|
||||
export class GraphPanel {
|
||||
private $panel: JQuery;
|
||||
private opts: GraphPanelOptions;
|
||||
private charts: Graph[] = [];
|
||||
private timer: number;
|
||||
|
||||
constructor(elems: string | Element | JQuery, opts?: GraphPanelOptions) {
|
||||
constructor(elem: string | Element | JQuery, opts?: GraphPanelOptions) {
|
||||
this.opts = $.extend(new GraphPanelOptions(), opts);
|
||||
|
||||
$(elems).each((i, e) => {
|
||||
this.$panel = $(elem);
|
||||
this.$panel.children().each((i, e) => {
|
||||
let g = GraphFactory.create(e);
|
||||
if (g != null) {
|
||||
this.charts.push(g);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(e => {
|
||||
$.each(this.charts, (i: number, g: Graph) => {
|
||||
g.resize(0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
@ -333,13 +355,83 @@ namespace Swirl.Core {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
addGraph(c: any) {
|
||||
for (let i =0; i< this.charts.length; i++) {
|
||||
let chart = this.charts[i];
|
||||
if (chart.getName() === c.name) {
|
||||
// chart already added.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let $chart = $(`<div class="column is-${c.width}" data-chart-name="${c.name}" data-chart-type="${c.type}" data-chart-unit="${c.unit}" data-chart-width="${c.width}" data-chart-height="${c.height}">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">${c.title}</p>
|
||||
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
|
||||
<span class="icon">
|
||||
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div style="height: ${c.height}px">
|
||||
<canvas id="canvas_${c.name}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
this.$panel.append($chart);
|
||||
|
||||
let g = GraphFactory.create($chart);
|
||||
if (g != null) {
|
||||
this.charts.push(g);
|
||||
}
|
||||
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
removeGraph(name: string) {
|
||||
// todo:
|
||||
let index:number;
|
||||
for (let i =0; i< this.charts.length; i++) {
|
||||
let c = this.charts[i];
|
||||
if (c.getName() === name) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
save() {
|
||||
let charts = this.charts.map(c => {
|
||||
return {
|
||||
name: c.getName(),
|
||||
width: c.getOptions().width,
|
||||
height: c.getOptions().height,
|
||||
// colors: ,
|
||||
}
|
||||
});
|
||||
let args = {
|
||||
name: this.opts.name,
|
||||
key: this.opts.key || '',
|
||||
charts: charts,
|
||||
};
|
||||
$ajax.post(`/system/chart/save_panel`, args).json<AjaxResult>((r: AjaxResult) => {
|
||||
if (!r.success) {
|
||||
Modal.alert(r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadData() {
|
||||
let args: any = {
|
||||
dashboard: this.opts.name,
|
||||
charts: this.charts.map(c => c.getName()).join(","),
|
||||
time: this.opts.time,
|
||||
};
|
||||
if (this.opts.id) {
|
||||
args.id = this.opts.id;
|
||||
if (this.opts.key) {
|
||||
args.key = this.opts.key;
|
||||
}
|
||||
$ajax.get(`/system/chart/data`, args).json((d: { [index: string]: Chart.ChartDataSets[] }) => {
|
||||
$.each(this.charts, (i: number, g: Graph) => {
|
||||
|
@ -3,15 +3,76 @@
|
||||
namespace Swirl {
|
||||
import Modal = Swirl.Core.Modal;
|
||||
import GraphPanel = Swirl.Core.GraphPanel;
|
||||
import FilterBox = Swirl.Core.FilterBox;
|
||||
|
||||
export class IndexPage {
|
||||
private panel: GraphPanel;
|
||||
private fb: FilterBox;
|
||||
private charts: any;
|
||||
private $charts: JQuery;
|
||||
|
||||
constructor() {
|
||||
this.panel = new GraphPanel($("#div-charts").children("div"), {name: "home"});
|
||||
$("#btn-add").click(() => {
|
||||
Modal.alert("Coming soon...");
|
||||
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
|
||||
this.panel = new GraphPanel("#div-charts", {name: "home"});
|
||||
$("#btn-add").click(this.showAddDlg.bind(this));
|
||||
$("#btn-add-chart").click(this.addChart.bind(this));
|
||||
$("#btn-save").click(() => {
|
||||
this.panel.save();
|
||||
});
|
||||
}
|
||||
|
||||
private showAddDlg() {
|
||||
let $panel = $("#nav-charts");
|
||||
$panel.find("label.panel-block").remove();
|
||||
|
||||
// load charts
|
||||
$ajax.get(`/system/chart/query`, {dashboard: "home"}).json((charts: any) => {
|
||||
for (let i = 0; i < charts.length; i++) {
|
||||
let c = charts[i];
|
||||
$panel.append(`<label class="panel-block">
|
||||
<input type="checkbox" value="${c.name}" data-index="${i}">${c.name}: ${c.title}
|
||||
</label>`);
|
||||
}
|
||||
this.charts = charts;
|
||||
this.$charts = $panel.find("label.panel-block");
|
||||
});
|
||||
|
||||
let dlg = new Modal("#dlg-add-chart");
|
||||
dlg.show();
|
||||
}
|
||||
|
||||
private filterCharts(text: string) {
|
||||
if (!text) {
|
||||
this.$charts.show();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$charts.each((i, elem) => {
|
||||
let $elem = $(elem);
|
||||
let texts: string[] = [
|
||||
this.charts[i].name.toLowerCase(),
|
||||
this.charts[i].title.toLowerCase(),
|
||||
this.charts[i].desc.toLowerCase(),
|
||||
];
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
let index = texts[i].indexOf(text);
|
||||
if (index >= 0) {
|
||||
$elem.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$elem.hide();
|
||||
})
|
||||
}
|
||||
|
||||
private addChart() {
|
||||
this.$charts.each((i, e) => {
|
||||
if ($(e).find(":checked").length > 0) {
|
||||
let c = this.charts[i];
|
||||
this.panel.addGraph(c);
|
||||
}
|
||||
});
|
||||
Modal.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,9 @@ namespace Swirl.Service {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel = new GraphPanel($("#div-charts").children("div"), {
|
||||
this.panel = new GraphPanel("#div-charts", {
|
||||
name: "service",
|
||||
id: $("#h2-service-name").text()
|
||||
key: $("#h2-service-name").text()
|
||||
});
|
||||
|
||||
$("#btn-add").click(() => {
|
||||
|
144
biz/chart.go
144
biz/chart.go
@ -1,22 +1,44 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/log"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz/docker"
|
||||
"github.com/cuigh/swirl/dao"
|
||||
"github.com/cuigh/swirl/model"
|
||||
)
|
||||
|
||||
// Chart return a chart biz instance.
|
||||
var Chart = &chartBiz{}
|
||||
var Chart = newChartBiz()
|
||||
|
||||
type chartBiz struct {
|
||||
builtin []*model.Chart
|
||||
}
|
||||
|
||||
func newChartBiz() *chartBiz {
|
||||
b := &chartBiz{}
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$cpu", "CPU", "${name}", `rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="${service}"}[5m]) * 100`, "percent:100"))
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$memory", "Memory", "${name}", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="${service}"}`, "size:bytes"))
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$network_in", "Network Receive", "${name}", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="${service}"}[5m])) by(name)`, "size:bytes"))
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$network_out", "Network Send", "${name}", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="${service}"}[5m])) by(name)`, "size:bytes"))
|
||||
// for _, c := range categories {
|
||||
// if c == "java" {
|
||||
// charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`, "time:s"))
|
||||
// } else if c == "go" {
|
||||
// charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `go_threads{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("goroutines", "Goroutines", "${instance}", `go_goroutines{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`, "time:s"))
|
||||
// }
|
||||
// }
|
||||
// for i, c := range charts {
|
||||
// charts[i].Query = fmt.Sprintf(c.Query, name)
|
||||
// }
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *chartBiz) List() (charts []*model.Chart, err error) {
|
||||
@ -28,8 +50,8 @@ func (b *chartBiz) List() (charts []*model.Chart, err error) {
|
||||
|
||||
func (b *chartBiz) Create(chart *model.Chart, user web.User) (err error) {
|
||||
do(func(d dao.Interface) {
|
||||
//chart.CreatedAt = time.Now()
|
||||
//chart.UpdatedAt = chart.CreatedAt
|
||||
// chart.CreatedAt = time.Now()
|
||||
// chart.UpdatedAt = chart.CreatedAt
|
||||
err = d.ChartCreate(chart)
|
||||
})
|
||||
return
|
||||
@ -49,42 +71,31 @@ func (b *chartBiz) Get(name string) (chart *model.Chart, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (b *chartBiz) Batch(names ...string) (charts []*model.Chart, err error) {
|
||||
do(func(d dao.Interface) {
|
||||
charts, err = d.ChartBatch(names...)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *chartBiz) Update(chart *model.Chart, user web.User) (err error) {
|
||||
do(func(d dao.Interface) {
|
||||
//chart.UpdatedAt = time.Now()
|
||||
// chart.UpdatedAt = time.Now()
|
||||
err = d.ChartUpdate(chart)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *chartBiz) GetServiceCharts(name string) (charts []*model.Chart, err error) {
|
||||
service, _, err := docker.ServiceInspect(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// service, _, err := docker.ServiceInspect(name)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
var categories []string
|
||||
if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
||||
categories = strings.Split(label, ",")
|
||||
}
|
||||
|
||||
charts = append(charts, model.NewChart("cpu", "CPU", "${name}", `rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`, "percent:100"))
|
||||
charts = append(charts, model.NewChart("memory", "Memory", "${name}", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`, "size:bytes"))
|
||||
charts = append(charts, model.NewChart("network_in", "Network Receive", "${name}", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`, "size:bytes"))
|
||||
charts = append(charts, model.NewChart("network_out", "Network Send", "${name}", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`, "size:bytes"))
|
||||
for _, c := range categories {
|
||||
if c == "java" {
|
||||
charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`, ""))
|
||||
charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`, "time:s"))
|
||||
} else if c == "go" {
|
||||
charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `go_threads{service="%s"}`, ""))
|
||||
charts = append(charts, model.NewChart("goroutines", "Goroutines", "${instance}", `go_goroutines{service="%s"}`, ""))
|
||||
charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`, "time:s"))
|
||||
}
|
||||
}
|
||||
for i, c := range charts {
|
||||
charts[i].Query = fmt.Sprintf(c.Query, name)
|
||||
}
|
||||
// if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
||||
// names := strings.Split(label, ",")
|
||||
// }
|
||||
charts = b.builtin
|
||||
return
|
||||
}
|
||||
|
||||
@ -130,24 +141,33 @@ func (b *chartBiz) Panel(panel model.ChartPanel) (charts []*model.Chart, err err
|
||||
return
|
||||
}
|
||||
|
||||
// todo:
|
||||
func (b *chartBiz) FetchDatas(charts []*model.Chart, period time.Duration) (data.Map, error) {
|
||||
func (b *chartBiz) FetchDatas(key string, names []string, period time.Duration) (data.Map, error) {
|
||||
charts, err := b.getCharts(names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datas := data.Map{}
|
||||
end := time.Now()
|
||||
start := end.Add(-period)
|
||||
for _, chart := range charts {
|
||||
query, err := b.formatQuery(chart, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch chart.Type {
|
||||
case "line", "bar":
|
||||
m, err := Metric.GetMatrix(chart.Query, chart.Label, start, end)
|
||||
m, err := Metric.GetMatrix(query, chart.Label, start, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
log.Get("metric").Error(err, query)
|
||||
} else {
|
||||
datas[chart.Name] = m
|
||||
}
|
||||
case "pie", "table":
|
||||
m, err := Metric.GetVector(chart.Query, chart.Label, end)
|
||||
m, err := Metric.GetVector(query, chart.Label, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
log.Get("metric").Error(err, query)
|
||||
} else {
|
||||
datas[chart.Name] = m
|
||||
}
|
||||
@ -156,3 +176,49 @@ func (b *chartBiz) FetchDatas(charts []*model.Chart, period time.Duration) (data
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (b *chartBiz) formatQuery(chart *model.Chart, key string) (string, error) {
|
||||
if chart.Dashboard == "home" {
|
||||
return chart.Query, nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
m := map[string]string{chart.Dashboard: key}
|
||||
query := os.Expand(chart.Query, func(k string) string {
|
||||
if v, ok := m[k]; ok {
|
||||
return v
|
||||
}
|
||||
errs = append(errs, errors.New("invalid argument in query: "+chart.Query))
|
||||
return ""
|
||||
})
|
||||
if len(errs) == 0 {
|
||||
return query, nil
|
||||
}
|
||||
return "", errs[0]
|
||||
}
|
||||
|
||||
func (b *chartBiz) getCharts(names []string) (charts []*model.Chart, err error) {
|
||||
var (
|
||||
customNames []string
|
||||
customCharts []*model.Chart
|
||||
)
|
||||
|
||||
for _, n := range names {
|
||||
if n[0] == '$' {
|
||||
for _, c := range b.builtin {
|
||||
if c.Name == n {
|
||||
charts = append(charts, c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
customNames = append(customNames, n)
|
||||
}
|
||||
}
|
||||
|
||||
if len(customNames) > 0 {
|
||||
if customCharts, err = b.Batch(customNames...); err == nil {
|
||||
charts = append(charts, customCharts...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@ -37,26 +36,26 @@ type metricBiz struct {
|
||||
api lazy.Value
|
||||
}
|
||||
|
||||
func (b *metricBiz) GetServiceCharts(service string, categories []string) (charts []model.ChartInfo) {
|
||||
charts = append(charts, model.NewChartInfo("cpu", "CPU", "${name}", `rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`))
|
||||
charts = append(charts, model.NewChartInfo("memory", "Memory", "${name}", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("network_in", "Network Receive", "${name}", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
charts = append(charts, model.NewChartInfo("network_out", "Network Send", "${name}", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
for _, c := range categories {
|
||||
if c == "java" {
|
||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`))
|
||||
} else if c == "go" {
|
||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `go_threads{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("goroutines", "Goroutines", "${instance}", `go_goroutines{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`))
|
||||
}
|
||||
}
|
||||
for i, c := range charts {
|
||||
charts[i].Query = fmt.Sprintf(c.Query, service)
|
||||
}
|
||||
return
|
||||
}
|
||||
// func (b *metricBiz) GetServiceCharts(service string, categories []string) (charts []model.ChartInfo) {
|
||||
// charts = append(charts, model.NewChartInfo("cpu", "CPU", "${name}", `rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`))
|
||||
// charts = append(charts, model.NewChartInfo("memory", "Memory", "${name}", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`))
|
||||
// charts = append(charts, model.NewChartInfo("network_in", "Network Receive", "${name}", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
// charts = append(charts, model.NewChartInfo("network_out", "Network Send", "${name}", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
// for _, c := range categories {
|
||||
// if c == "java" {
|
||||
// charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`))
|
||||
// charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`))
|
||||
// } else if c == "go" {
|
||||
// charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `go_threads{service="%s"}`))
|
||||
// charts = append(charts, model.NewChartInfo("goroutines", "Goroutines", "${instance}", `go_goroutines{service="%s"}`))
|
||||
// charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`))
|
||||
// }
|
||||
// }
|
||||
// for i, c := range charts {
|
||||
// charts[i].Query = fmt.Sprintf(c.Query, service)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines []model.ChartLine, err error) {
|
||||
api, err := b.getAPI()
|
||||
|
@ -33,3 +33,13 @@ func (b *settingBiz) Update(setting *model.Setting, user web.User) (err error) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *settingBiz) UpdateDashboard(name string, dashboard *model.ChartPanel, user web.User) (err error) {
|
||||
do(func(d dao.Interface) {
|
||||
err = d.UpdateDashboard(name, dashboard)
|
||||
if err == nil {
|
||||
Event.CreateSetting(model.EventActionUpdate, user)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/util/cast"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
@ -11,27 +13,29 @@ import (
|
||||
|
||||
// ChartController is a controller of metric chart.
|
||||
type ChartController struct {
|
||||
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
||||
Query web.HandlerFunc `path:"/query" name:"chart.query" authorize:"?" desc:"chart query"`
|
||||
New web.HandlerFunc `path:"/new" name:"chart.new" authorize:"!" desc:"new chart page"`
|
||||
Create web.HandlerFunc `path:"/new" method:"post" name:"chart.create" authorize:"!" desc:"create chart"`
|
||||
Edit web.HandlerFunc `path:"/:name/edit" name:"chart.edit" authorize:"!" desc:"edit chart page"`
|
||||
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"chart.delete" authorize:"!" desc:"delete chart"`
|
||||
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"chart.update" authorize:"!" desc:"update chart"`
|
||||
Data web.HandlerFunc `path:"/data" name:"chart.data" authorize:"?" desc:"fetch chart datas"`
|
||||
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
||||
Query web.HandlerFunc `path:"/query" name:"chart.query" authorize:"?" desc:"chart query"`
|
||||
New web.HandlerFunc `path:"/new" name:"chart.new" authorize:"!" desc:"new chart page"`
|
||||
Create web.HandlerFunc `path:"/new" method:"post" name:"chart.create" authorize:"!" desc:"create chart"`
|
||||
Edit web.HandlerFunc `path:"/:name/edit" name:"chart.edit" authorize:"!" desc:"edit chart page"`
|
||||
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"chart.delete" authorize:"!" desc:"delete chart"`
|
||||
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"chart.update" authorize:"!" desc:"update chart"`
|
||||
Data web.HandlerFunc `path:"/data" name:"chart.data" authorize:"?" desc:"fetch chart datas"`
|
||||
SavePanel web.HandlerFunc `path:"/save_panel" method:"post" name:"chart.save_panel" authorize:"!" desc:"save panel"`
|
||||
}
|
||||
|
||||
// Chart creates an instance of RoleController
|
||||
func Chart() (c *ChartController) {
|
||||
return &ChartController{
|
||||
List: chartList,
|
||||
Query: chartQuery,
|
||||
New: chartNew,
|
||||
Create: chartCreate,
|
||||
Edit: chartEdit,
|
||||
Update: chartUpdate,
|
||||
Delete: chartDelete,
|
||||
Data: chartData,
|
||||
List: chartList,
|
||||
Query: chartQuery,
|
||||
New: chartNew,
|
||||
Create: chartCreate,
|
||||
Edit: chartEdit,
|
||||
Update: chartUpdate,
|
||||
Delete: chartDelete,
|
||||
Data: chartData,
|
||||
SavePanel: chartSavePanel,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +68,7 @@ func chartQuery(ctx web.Context) error {
|
||||
func chartNew(ctx web.Context) error {
|
||||
m := newModel(ctx).Set("Chart", &model.Chart{
|
||||
Width: 12,
|
||||
Height: 50,
|
||||
Height: 150,
|
||||
Type: "line",
|
||||
Dashboard: "service",
|
||||
})
|
||||
@ -109,35 +113,33 @@ func chartDelete(ctx web.Context) error {
|
||||
return ajaxResult(ctx, err)
|
||||
}
|
||||
|
||||
// todo:
|
||||
func chartData(ctx web.Context) error {
|
||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||
dashboard := ctx.Q("dashboard")
|
||||
|
||||
var (
|
||||
charts []*model.Chart
|
||||
err error
|
||||
)
|
||||
if dashboard == "home" {
|
||||
var setting *model.Setting
|
||||
setting, err = biz.Setting.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
charts, err = biz.Chart.Panel(setting.Dashboard.Home)
|
||||
|
||||
} else if dashboard == "service" {
|
||||
id := ctx.Q("id")
|
||||
charts, err = biz.Chart.GetServiceCharts(id)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
datas, err := biz.Chart.FetchDatas(charts, period)
|
||||
charts := strings.Split(ctx.Q("charts"), ",")
|
||||
key := ctx.Q("key")
|
||||
datas, err := biz.Chart.FetchDatas(key, charts, period)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.JSON(datas)
|
||||
}
|
||||
|
||||
func chartSavePanel(ctx web.Context) error {
|
||||
data := struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
model.ChartPanel
|
||||
}{}
|
||||
err := ctx.Bind(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch data.Name {
|
||||
case "home":
|
||||
err = biz.Setting.UpdateDashboard(data.Name, &data.ChartPanel, ctx.User())
|
||||
default:
|
||||
err = errors.New("unknown dashboard: " + data.Name)
|
||||
}
|
||||
return ajaxResult(ctx, err)
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ type Interface interface {
|
||||
|
||||
SettingGet() (setting *model.Setting, err error)
|
||||
SettingUpdate(setting *model.Setting) error
|
||||
UpdateDashboard(name string, dashboard *model.ChartPanel) error
|
||||
|
||||
ChartGet(name string) (*model.Chart, error)
|
||||
ChartBatch(names ...string) ([]*model.Chart, error)
|
||||
|
@ -28,3 +28,17 @@ func (d *Dao) SettingUpdate(setting *model.Setting) (err error) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Dao) UpdateDashboard(name string, dashboard *model.ChartPanel) (err error) {
|
||||
d.do(func(db *database) {
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"dashboard": bson.M{
|
||||
name: dashboard,
|
||||
},
|
||||
},
|
||||
}
|
||||
err = db.C("setting").UpdateId(settingID, update)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -21,17 +21,18 @@ type Chart struct {
|
||||
Options data.Map `json:"options"`
|
||||
}
|
||||
|
||||
func NewChart(name, title, label, query, unit string) *Chart {
|
||||
func NewChart(dashboard, name, title, label, query, unit string) *Chart {
|
||||
return &Chart{
|
||||
Name: name,
|
||||
Title: title,
|
||||
Description: title,
|
||||
Label: label,
|
||||
Query: query,
|
||||
Dashboard: dashboard,
|
||||
Type: "line",
|
||||
Unit: unit,
|
||||
Width: 12,
|
||||
Height: 50,
|
||||
Height: 150,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,13 +73,20 @@
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<button id="btn-add" class="button is-success"><span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.add") }}</span></button>
|
||||
<button id="btn-add" class="button is-success">
|
||||
<span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.add") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<button id="btn-save" class="button is-info">
|
||||
<span class="icon"><i class="fas fa-save"></i></span><span>{{ i18n("button.save") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="div-charts" class="columns is-multiline">
|
||||
{{ range .Charts }}
|
||||
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-height="{{ .Height }}">
|
||||
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-width="{{ .Width }}" data-chart-height="{{ .Height }}">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">{{ .Title }}</p>
|
||||
@ -100,11 +107,39 @@
|
||||
{*</a>*}
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||
<div style="height: {{ .Height }}px">
|
||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="dlg-add-chart" class="modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Add chart</p>
|
||||
<button class="delete"></button>
|
||||
</header>
|
||||
<section class="modal-card-body" style="max-height: 400px; overflow-y: auto">
|
||||
<nav id="nav-charts" class="panel">
|
||||
<div class="panel-block">
|
||||
<p class="control has-icons-left">
|
||||
<input id="txt-query" class="input is-small" type="text" placeholder="Searching chart...">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</nav>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button id="btn-add-chart" type="button" class="button is-primary">{{ i18n("button.confirm") }}</button>
|
||||
<button type="button" class="button dismiss">{{ i18n("button.cancel") }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
@ -76,11 +76,14 @@
|
||||
<div class="level-item">
|
||||
<button id="btn-add" class="button is-success"><span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.add") }}</span></button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<button id="btn-save" class="button is-info"><span class="icon"><i class="fas fa-save"></i></span><span>{{ i18n("button.save") }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="div-charts" class="columns is-multiline">
|
||||
{{ range .Charts }}
|
||||
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-height="{{ .Height }}">
|
||||
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-width="{{ .Width }}" data-chart-height="{{ .Height }}">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">{{ .Title }}</p>
|
||||
@ -96,7 +99,9 @@
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||
<div style="height: {{ .Height }}px">
|
||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user