mirror of
https://github.com/cuigh/swirl
synced 2024-12-29 07:12:11 +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;
|
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 = {}));
|
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
/*!
|
/*!
|
||||||
@ -1104,6 +1117,461 @@ var Swirl;
|
|||||||
})(Core = Swirl.Core || (Swirl.Core = {}));
|
})(Core = Swirl.Core || (Swirl.Core = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
var 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) {
|
(function (Swirl) {
|
||||||
var Config;
|
var Config;
|
||||||
(function (Config) {
|
(function (Config) {
|
||||||
@ -1857,270 +2325,6 @@ var Swirl;
|
|||||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
var 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) {
|
(function (Swirl) {
|
||||||
var Service;
|
var Service;
|
||||||
(function (Service) {
|
(function (Service) {
|
||||||
@ -2132,9 +2336,9 @@ var Swirl;
|
|||||||
if ($cb_time.length == 0) {
|
if ($cb_time.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.panel = new GraphPanel($("#div-charts").children("div"), {
|
this.panel = new GraphPanel("#div-charts", {
|
||||||
name: "service",
|
name: "service",
|
||||||
id: $("#h2-service-name").text()
|
key: $("#h2-service-name").text()
|
||||||
});
|
});
|
||||||
$("#btn-add").click(() => {
|
$("#btn-add").click(() => {
|
||||||
Modal.alert("Coming soon...");
|
Modal.alert("Coming soon...");
|
||||||
@ -2454,77 +2658,4 @@ var Swirl;
|
|||||||
Volume.NewPage = NewPage;
|
Volume.NewPage = NewPage;
|
||||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(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
|
//# 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 Modal = Swirl.Core.Modal;
|
||||||
import AjaxResult = Swirl.Core.AjaxResult;
|
import AjaxResult = Swirl.Core.AjaxResult;
|
||||||
import Dispatcher = Swirl.Core.Dispatcher;
|
import Dispatcher = Swirl.Core.Dispatcher;
|
||||||
|
import FilterBox = Swirl.Core.FilterBox;
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ListPage {
|
export class ListPage {
|
||||||
private fb: FilterBox;
|
private fb: FilterBox;
|
||||||
@ -59,11 +44,11 @@ namespace Swirl.Metric {
|
|||||||
for (let i = 0; i<texts.length; i++) {
|
for (let i = 0; i<texts.length; i++) {
|
||||||
let index = texts[i].indexOf(text);
|
let index = texts[i].indexOf(text);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
$(elem).show();
|
$elem.show();
|
||||||
return;
|
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";
|
type?: string = "line";
|
||||||
unit?: string;
|
unit?: string;
|
||||||
width?: number = 12;
|
width?: number = 12;
|
||||||
height?: number = 50;
|
height?: number = 150;
|
||||||
colors?: string[];
|
colors?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,15 @@ namespace Swirl.Core {
|
|||||||
return this.name;
|
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;
|
abstract setData(d: any): void;
|
||||||
}
|
}
|
||||||
@ -40,7 +48,7 @@ namespace Swirl.Core {
|
|||||||
setData(d: any): void {
|
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,
|
type: opts.type,
|
||||||
data: {},
|
data: {},
|
||||||
options: {
|
options: {
|
||||||
|
responsive: false,
|
||||||
|
maintainAspectRatio: false,
|
||||||
// title: {
|
// title: {
|
||||||
// // display: true,
|
// // display: true,
|
||||||
// text: opts.title || 'NONE'
|
// text: opts.title || 'NONE'
|
||||||
@ -106,7 +116,8 @@ namespace Swirl.Core {
|
|||||||
|
|
||||||
this.ctx = (<HTMLCanvasElement>$(elem).find("canvas").get(0)).getContext('2d');
|
this.ctx = (<HTMLCanvasElement>$(elem).find("canvas").get(0)).getContext('2d');
|
||||||
if (opts.height) {
|
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);
|
this.chart = new Chart(this.ctx, this.config);
|
||||||
}
|
}
|
||||||
@ -114,9 +125,12 @@ namespace Swirl.Core {
|
|||||||
setData(d: any): void {
|
setData(d: any): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(w: number, h: number): void {
|
resize(w: number, h: number): void {
|
||||||
this.ctx.canvas.height = h;
|
// this.ctx.canvas.style.width = this.ctx.canvas.parentElement.offsetWidth + "px";
|
||||||
this.chart.update();
|
// 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() {
|
protected fillConfig() {
|
||||||
@ -266,6 +280,7 @@ namespace Swirl.Core {
|
|||||||
let opts: GraphOptions = {
|
let opts: GraphOptions = {
|
||||||
type: $elem.data("chart-type"),
|
type: $elem.data("chart-type"),
|
||||||
unit: $elem.data("chart-unit"),
|
unit: $elem.data("chart-unit"),
|
||||||
|
width: $elem.data("chart-width"),
|
||||||
height: $elem.data("chart-height"),
|
height: $elem.data("chart-height"),
|
||||||
colors: $elem.data("chart-colors"),
|
colors: $elem.data("chart-colors"),
|
||||||
};
|
};
|
||||||
@ -284,26 +299,33 @@ namespace Swirl.Core {
|
|||||||
|
|
||||||
export class GraphPanelOptions {
|
export class GraphPanelOptions {
|
||||||
name: string;
|
name: string;
|
||||||
id?: string;
|
key?: string;
|
||||||
time?: string = "30m";
|
time?: string = "30m";
|
||||||
refreshInterval?: number = 15000; // ms
|
refreshInterval?: number = 15000; // ms
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphPanel {
|
export class GraphPanel {
|
||||||
|
private $panel: JQuery;
|
||||||
private opts: GraphPanelOptions;
|
private opts: GraphPanelOptions;
|
||||||
private charts: Graph[] = [];
|
private charts: Graph[] = [];
|
||||||
private timer: number;
|
private timer: number;
|
||||||
|
|
||||||
constructor(elems: string | Element | JQuery, opts?: GraphPanelOptions) {
|
constructor(elem: string | Element | JQuery, opts?: GraphPanelOptions) {
|
||||||
this.opts = $.extend(new GraphPanelOptions(), opts);
|
this.opts = $.extend(new GraphPanelOptions(), opts);
|
||||||
|
this.$panel = $(elem);
|
||||||
$(elems).each((i, e) => {
|
this.$panel.children().each((i, e) => {
|
||||||
let g = GraphFactory.create(e);
|
let g = GraphFactory.create(e);
|
||||||
if (g != null) {
|
if (g != null) {
|
||||||
this.charts.push(g);
|
this.charts.push(g);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(window).resize(e => {
|
||||||
|
$.each(this.charts, (i: number, g: Graph) => {
|
||||||
|
g.resize(0, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,13 +355,83 @@ namespace Swirl.Core {
|
|||||||
this.loadData();
|
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() {
|
private loadData() {
|
||||||
let args: any = {
|
let args: any = {
|
||||||
dashboard: this.opts.name,
|
charts: this.charts.map(c => c.getName()).join(","),
|
||||||
time: this.opts.time,
|
time: this.opts.time,
|
||||||
};
|
};
|
||||||
if (this.opts.id) {
|
if (this.opts.key) {
|
||||||
args.id = this.opts.id;
|
args.key = this.opts.key;
|
||||||
}
|
}
|
||||||
$ajax.get(`/system/chart/data`, args).json((d: { [index: string]: Chart.ChartDataSets[] }) => {
|
$ajax.get(`/system/chart/data`, args).json((d: { [index: string]: Chart.ChartDataSets[] }) => {
|
||||||
$.each(this.charts, (i: number, g: Graph) => {
|
$.each(this.charts, (i: number, g: Graph) => {
|
||||||
|
@ -3,15 +3,76 @@
|
|||||||
namespace Swirl {
|
namespace Swirl {
|
||||||
import Modal = Swirl.Core.Modal;
|
import Modal = Swirl.Core.Modal;
|
||||||
import GraphPanel = Swirl.Core.GraphPanel;
|
import GraphPanel = Swirl.Core.GraphPanel;
|
||||||
|
import FilterBox = Swirl.Core.FilterBox;
|
||||||
|
|
||||||
export class IndexPage {
|
export class IndexPage {
|
||||||
private panel: GraphPanel;
|
private panel: GraphPanel;
|
||||||
|
private fb: FilterBox;
|
||||||
|
private charts: any;
|
||||||
|
private $charts: JQuery;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.panel = new GraphPanel($("#div-charts").children("div"), {name: "home"});
|
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
|
||||||
$("#btn-add").click(() => {
|
this.panel = new GraphPanel("#div-charts", {name: "home"});
|
||||||
Modal.alert("Coming soon...");
|
$("#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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.panel = new GraphPanel($("#div-charts").children("div"), {
|
this.panel = new GraphPanel("#div-charts", {
|
||||||
name: "service",
|
name: "service",
|
||||||
id: $("#h2-service-name").text()
|
key: $("#h2-service-name").text()
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btn-add").click(() => {
|
$("#btn-add").click(() => {
|
||||||
|
144
biz/chart.go
144
biz/chart.go
@ -1,22 +1,44 @@
|
|||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cuigh/auxo/data"
|
"github.com/cuigh/auxo/data"
|
||||||
|
"github.com/cuigh/auxo/errors"
|
||||||
"github.com/cuigh/auxo/log"
|
"github.com/cuigh/auxo/log"
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
"github.com/cuigh/swirl/biz/docker"
|
|
||||||
"github.com/cuigh/swirl/dao"
|
"github.com/cuigh/swirl/dao"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chart return a chart biz instance.
|
// Chart return a chart biz instance.
|
||||||
var Chart = &chartBiz{}
|
var Chart = newChartBiz()
|
||||||
|
|
||||||
type chartBiz struct {
|
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) {
|
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) {
|
func (b *chartBiz) Create(chart *model.Chart, user web.User) (err error) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
//chart.CreatedAt = time.Now()
|
// chart.CreatedAt = time.Now()
|
||||||
//chart.UpdatedAt = chart.CreatedAt
|
// chart.UpdatedAt = chart.CreatedAt
|
||||||
err = d.ChartCreate(chart)
|
err = d.ChartCreate(chart)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -49,42 +71,31 @@ func (b *chartBiz) Get(name string) (chart *model.Chart, err error) {
|
|||||||
return
|
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) {
|
func (b *chartBiz) Update(chart *model.Chart, user web.User) (err error) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
//chart.UpdatedAt = time.Now()
|
// chart.UpdatedAt = time.Now()
|
||||||
err = d.ChartUpdate(chart)
|
err = d.ChartUpdate(chart)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *chartBiz) GetServiceCharts(name string) (charts []*model.Chart, err error) {
|
func (b *chartBiz) GetServiceCharts(name string) (charts []*model.Chart, err error) {
|
||||||
service, _, err := docker.ServiceInspect(name)
|
// service, _, err := docker.ServiceInspect(name)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var categories []string
|
// if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
||||||
if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
// names := strings.Split(label, ",")
|
||||||
categories = strings.Split(label, ",")
|
// }
|
||||||
}
|
charts = b.builtin
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,24 +141,33 @@ func (b *chartBiz) Panel(panel model.ChartPanel) (charts []*model.Chart, err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo:
|
func (b *chartBiz) FetchDatas(key string, names []string, period time.Duration) (data.Map, error) {
|
||||||
func (b *chartBiz) FetchDatas(charts []*model.Chart, period time.Duration) (data.Map, error) {
|
charts, err := b.getCharts(names)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
datas := data.Map{}
|
datas := data.Map{}
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
start := end.Add(-period)
|
start := end.Add(-period)
|
||||||
for _, chart := range charts {
|
for _, chart := range charts {
|
||||||
|
query, err := b.formatQuery(chart, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch chart.Type {
|
switch chart.Type {
|
||||||
case "line", "bar":
|
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 {
|
if err != nil {
|
||||||
log.Get("metric").Error(err)
|
log.Get("metric").Error(err, query)
|
||||||
} else {
|
} else {
|
||||||
datas[chart.Name] = m
|
datas[chart.Name] = m
|
||||||
}
|
}
|
||||||
case "pie", "table":
|
case "pie", "table":
|
||||||
m, err := Metric.GetVector(chart.Query, chart.Label, end)
|
m, err := Metric.GetVector(query, chart.Label, end)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Get("metric").Error(err)
|
log.Get("metric").Error(err, query)
|
||||||
} else {
|
} else {
|
||||||
datas[chart.Name] = m
|
datas[chart.Name] = m
|
||||||
}
|
}
|
||||||
@ -156,3 +176,49 @@ func (b *chartBiz) FetchDatas(charts []*model.Chart, period time.Duration) (data
|
|||||||
}
|
}
|
||||||
return datas, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -37,26 +36,26 @@ type metricBiz struct {
|
|||||||
api lazy.Value
|
api lazy.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *metricBiz) GetServiceCharts(service string, categories []string) (charts []model.ChartInfo) {
|
// 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("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("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_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)`))
|
// 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 {
|
// for _, c := range categories {
|
||||||
if c == "java" {
|
// if c == "java" {
|
||||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`))
|
// 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])`))
|
// charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`))
|
||||||
} else if c == "go" {
|
// } else if c == "go" {
|
||||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "${instance}", `go_threads{service="%s"}`))
|
// 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("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)`))
|
// charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
for i, c := range charts {
|
// for i, c := range charts {
|
||||||
charts[i].Query = fmt.Sprintf(c.Query, service)
|
// charts[i].Query = fmt.Sprintf(c.Query, service)
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines []model.ChartLine, err error) {
|
func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines []model.ChartLine, err error) {
|
||||||
api, err := b.getAPI()
|
api, err := b.getAPI()
|
||||||
|
@ -33,3 +33,13 @@ func (b *settingBiz) Update(setting *model.Setting, user web.User) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cuigh/auxo/errors"
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
"github.com/cuigh/auxo/util/cast"
|
"github.com/cuigh/auxo/util/cast"
|
||||||
"github.com/cuigh/swirl/biz"
|
"github.com/cuigh/swirl/biz"
|
||||||
@ -11,27 +13,29 @@ import (
|
|||||||
|
|
||||||
// ChartController is a controller of metric chart.
|
// ChartController is a controller of metric chart.
|
||||||
type ChartController struct {
|
type ChartController struct {
|
||||||
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
||||||
Query web.HandlerFunc `path:"/query" name:"chart.query" authorize:"?" desc:"chart query"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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
|
// Chart creates an instance of RoleController
|
||||||
func Chart() (c *ChartController) {
|
func Chart() (c *ChartController) {
|
||||||
return &ChartController{
|
return &ChartController{
|
||||||
List: chartList,
|
List: chartList,
|
||||||
Query: chartQuery,
|
Query: chartQuery,
|
||||||
New: chartNew,
|
New: chartNew,
|
||||||
Create: chartCreate,
|
Create: chartCreate,
|
||||||
Edit: chartEdit,
|
Edit: chartEdit,
|
||||||
Update: chartUpdate,
|
Update: chartUpdate,
|
||||||
Delete: chartDelete,
|
Delete: chartDelete,
|
||||||
Data: chartData,
|
Data: chartData,
|
||||||
|
SavePanel: chartSavePanel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ func chartQuery(ctx web.Context) error {
|
|||||||
func chartNew(ctx web.Context) error {
|
func chartNew(ctx web.Context) error {
|
||||||
m := newModel(ctx).Set("Chart", &model.Chart{
|
m := newModel(ctx).Set("Chart", &model.Chart{
|
||||||
Width: 12,
|
Width: 12,
|
||||||
Height: 50,
|
Height: 150,
|
||||||
Type: "line",
|
Type: "line",
|
||||||
Dashboard: "service",
|
Dashboard: "service",
|
||||||
})
|
})
|
||||||
@ -109,35 +113,33 @@ func chartDelete(ctx web.Context) error {
|
|||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo:
|
|
||||||
func chartData(ctx web.Context) error {
|
func chartData(ctx web.Context) error {
|
||||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||||
dashboard := ctx.Q("dashboard")
|
charts := strings.Split(ctx.Q("charts"), ",")
|
||||||
|
key := ctx.Q("key")
|
||||||
var (
|
datas, err := biz.Chart.FetchDatas(key, charts, period)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ctx.JSON(datas)
|
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)
|
SettingGet() (setting *model.Setting, err error)
|
||||||
SettingUpdate(setting *model.Setting) error
|
SettingUpdate(setting *model.Setting) error
|
||||||
|
UpdateDashboard(name string, dashboard *model.ChartPanel) error
|
||||||
|
|
||||||
ChartGet(name string) (*model.Chart, error)
|
ChartGet(name string) (*model.Chart, error)
|
||||||
ChartBatch(names ...string) ([]*model.Chart, error)
|
ChartBatch(names ...string) ([]*model.Chart, error)
|
||||||
|
@ -28,3 +28,17 @@ func (d *Dao) SettingUpdate(setting *model.Setting) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
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"`
|
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{
|
return &Chart{
|
||||||
Name: name,
|
Name: name,
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: title,
|
Description: title,
|
||||||
Label: label,
|
Label: label,
|
||||||
Query: query,
|
Query: query,
|
||||||
|
Dashboard: dashboard,
|
||||||
Type: "line",
|
Type: "line",
|
||||||
Unit: unit,
|
Unit: unit,
|
||||||
Width: 12,
|
Width: 12,
|
||||||
Height: 50,
|
Height: 150,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +73,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="level-item">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="div-charts" class="columns is-multiline">
|
<div id="div-charts" class="columns is-multiline">
|
||||||
{{ range .Charts }}
|
{{ 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">
|
<div class="card">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">{{ .Title }}</p>
|
<p class="card-header-title">{{ .Title }}</p>
|
||||||
@ -100,11 +107,39 @@
|
|||||||
{*</a>*}
|
{*</a>*}
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
<div style="height: {{ .Height }}px">
|
||||||
|
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 }}
|
{{ end }}
|
@ -76,11 +76,14 @@
|
|||||||
<div class="level-item">
|
<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>
|
||||||
|
<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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="div-charts" class="columns is-multiline">
|
<div id="div-charts" class="columns is-multiline">
|
||||||
{{ range .Charts }}
|
{{ 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">
|
<div class="card">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">{{ .Title }}</p>
|
<p class="card-header-title">{{ .Title }}</p>
|
||||||
@ -96,7 +99,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
<div style="height: {{ .Height }}px">
|
||||||
|
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user