mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Add service stats page based on Prometheus
This commit is contained in:
parent
5b60a024c5
commit
6fff495cda
19
Gopkg.lock
generated
19
Gopkg.lock
generated
@ -58,7 +58,7 @@
|
||||
"util/i18n",
|
||||
"util/lazy"
|
||||
]
|
||||
revision = "ac7f3a5d4d43fd755008c87525a95698544843ec"
|
||||
revision = "772f5a654db9ee95a5dc851e9562253bc0b2f11d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -169,6 +169,21 @@
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = [
|
||||
"api",
|
||||
"api/prometheus/v1"
|
||||
]
|
||||
revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
|
||||
version = "v0.9.0-pre1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = ["model"]
|
||||
revision = "6fb6fce6f8b75884b92e1889c150403fc0872c5e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
@ -209,6 +224,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "aae5ea1cf0fb5fd50cb9659fb0abe58fc9546231ec91f9df9148c5df3e3cc194"
|
||||
inputs-digest = "c829e2bd1f0dc356225caad79652e6732d0572e0e59580a5e6c5b6baab134755"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -9,6 +9,7 @@ Swirl is a web management tool for Docker, focused on swarm cluster.
|
||||
* Swarm components management
|
||||
* Image and container management
|
||||
* Compose management with deployment support
|
||||
* Service monitoring based on Prometheus
|
||||
* LDAP authentication support
|
||||
* Full permission control based on RBAC model
|
||||
* Scale out as you want
|
||||
|
18919
assets/chart/chart.bundle.js
Normal file
18919
assets/chart/chart.bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
10
assets/chart/chart.bundle.min.js
vendored
Normal file
10
assets/chart/chart.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -2155,4 +2155,137 @@ var Swirl;
|
||||
Volume.NewPage = NewPage;
|
||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Service;
|
||||
(function (Service) {
|
||||
class MetricChartOptions {
|
||||
constructor() {
|
||||
this.type = "line";
|
||||
this.height = 50;
|
||||
}
|
||||
}
|
||||
class MetricData {
|
||||
}
|
||||
class MetricChart {
|
||||
constructor(elem, opts) {
|
||||
this.colors = [
|
||||
'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)',
|
||||
];
|
||||
opts = $.extend(new MetricChartOptions(), opts);
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
title: {
|
||||
text: opts.title || 'NONE'
|
||||
},
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
yAxes: [{}]
|
||||
},
|
||||
}
|
||||
};
|
||||
if (opts.labelX) {
|
||||
this.config.options.scales.xAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelX,
|
||||
};
|
||||
}
|
||||
if (opts.labelY) {
|
||||
this.config.options.scales.yAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelY,
|
||||
};
|
||||
}
|
||||
if (opts.tickY) {
|
||||
this.config.options.scales.yAxes[0].ticks = {
|
||||
callback: opts.tickY,
|
||||
};
|
||||
}
|
||||
let ctx = $(elem).get(0).getContext('2d');
|
||||
if (opts.height) {
|
||||
ctx.canvas.height = opts.height;
|
||||
}
|
||||
this.chart = new Chart(ctx, this.config);
|
||||
}
|
||||
setData(datasets) {
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.colors.length) ? this.colors[i] : this.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 = datasets;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
class StatsPage {
|
||||
constructor() {
|
||||
let $cb_time = $("#cb-time");
|
||||
if ($cb_time.length == 0) {
|
||||
return;
|
||||
}
|
||||
$cb_time.change(this.loadData.bind(this));
|
||||
$("#cb-refresh").change(() => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
else {
|
||||
this.refreshData();
|
||||
}
|
||||
});
|
||||
this.cpu = new MetricChart("#canvas-cpu", {
|
||||
tickY: function (value) {
|
||||
return value + '%';
|
||||
},
|
||||
});
|
||||
this.memory = new MetricChart("#canvas-memory", {
|
||||
tickY: function (value) {
|
||||
return value < 1024 ? (value + 'M') : (value / 1024) + 'G';
|
||||
},
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
refreshData() {
|
||||
this.loadData();
|
||||
if ($("#cb-refresh").prop("checked")) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), 15000);
|
||||
}
|
||||
}
|
||||
loadData() {
|
||||
let time = $("#cb-time").val();
|
||||
$ajax.get(`metrics`, { time: time }).json((d) => {
|
||||
if (d.cpu) {
|
||||
this.cpu.setData(d.cpu);
|
||||
}
|
||||
if (d.memory) {
|
||||
this.memory.setData(d.memory);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Service.StatsPage = StatsPage;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
156
assets/swirl/ts/service/stats.ts
Normal file
156
assets/swirl/ts/service/stats.ts
Normal file
@ -0,0 +1,156 @@
|
||||
///<reference path="../core/core.ts" />
|
||||
namespace Swirl.Service {
|
||||
class MetricChartOptions {
|
||||
type?: string = "line";
|
||||
height?: number = 50;
|
||||
title?: string;
|
||||
labelX?: string;
|
||||
labelY?: string;
|
||||
tickY?: (value: number) => string;
|
||||
}
|
||||
|
||||
class MetricData {
|
||||
cpu?: Chart.ChartDataSets[];
|
||||
memory?: Chart.ChartDataSets[];
|
||||
}
|
||||
|
||||
class MetricChart {
|
||||
private chart: any;
|
||||
private readonly config: any;
|
||||
private colors = [
|
||||
'rgb(255, 99, 132)', // red
|
||||
'rgb(75, 192, 192)', // green
|
||||
'rgb(255, 159, 64)', // orange
|
||||
'rgb(54, 162, 235)', // blue
|
||||
'rgb(153, 102, 255)', // purple
|
||||
'rgb(255, 205, 86)', // yellow
|
||||
'rgb(201, 203, 207)', // grey
|
||||
];
|
||||
|
||||
constructor(elem: string | Element, opts: MetricChartOptions) {
|
||||
opts = $.extend(new MetricChartOptions(), opts);
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
title: {
|
||||
// display: true,
|
||||
text: opts.title || 'NONE'
|
||||
},
|
||||
// legend: {
|
||||
// position: "bottom"
|
||||
// },
|
||||
animation: {
|
||||
duration: 0,
|
||||
// easing: 'easeOutBounce',
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
// min: new Date(),
|
||||
// max: new Date(),
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
yAxes: [{}]
|
||||
},
|
||||
}
|
||||
};
|
||||
if (opts.labelX) {
|
||||
this.config.options.scales.xAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelX,
|
||||
}
|
||||
}
|
||||
if (opts.labelY) {
|
||||
this.config.options.scales.yAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelY,
|
||||
}
|
||||
}
|
||||
if (opts.tickY) {
|
||||
this.config.options.scales.yAxes[0].ticks = {
|
||||
callback: opts.tickY,
|
||||
}
|
||||
}
|
||||
|
||||
let ctx = (<HTMLCanvasElement>$(elem).get(0)).getContext('2d');
|
||||
if (opts.height) {
|
||||
ctx.canvas.height = opts.height;
|
||||
}
|
||||
this.chart = new Chart(ctx, this.config);
|
||||
}
|
||||
|
||||
public setData(datasets: Chart.ChartDataSets[]) {
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.colors.length) ? this.colors[i] : this.colors[0];
|
||||
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
|
||||
ds.borderColor = color;
|
||||
ds.borderWidth = 2;
|
||||
ds.pointRadius = 2;
|
||||
// ds.fill = false;
|
||||
});
|
||||
this.config.data.datasets = datasets;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
|
||||
export class StatsPage {
|
||||
private cpu: MetricChart;
|
||||
private memory: MetricChart;
|
||||
private timer: number;
|
||||
|
||||
constructor() {
|
||||
let $cb_time = $("#cb-time");
|
||||
if ($cb_time.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cb_time.change(this.loadData.bind(this));
|
||||
$("#cb-refresh").change(() => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
} else {
|
||||
this.refreshData();
|
||||
}
|
||||
});
|
||||
|
||||
this.cpu = new MetricChart("#canvas-cpu", {
|
||||
tickY: function (value: number): string {
|
||||
return value + '%';
|
||||
},
|
||||
});
|
||||
this.memory = new MetricChart("#canvas-memory", {
|
||||
tickY: function (value: number): string {
|
||||
return value < 1024 ? (value + 'M') : (value / 1024) + 'G';
|
||||
},
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
private refreshData() {
|
||||
this.loadData();
|
||||
if ($("#cb-refresh").prop("checked")) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), 15000);
|
||||
}
|
||||
}
|
||||
|
||||
private loadData() {
|
||||
let time = $("#cb-time").val();
|
||||
$ajax.get(`metrics`, {time: time}).json((d: MetricData) => {
|
||||
if (d.cpu) {
|
||||
this.cpu.setData(d.cpu);
|
||||
}
|
||||
if (d.memory) {
|
||||
this.memory.setData(d.memory);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
590
assets/swirl/typings/chart.d.ts
vendored
Normal file
590
assets/swirl/typings/chart.d.ts
vendored
Normal file
@ -0,0 +1,590 @@
|
||||
// Type definitions for Chart.js 2.7
|
||||
// Project: https://github.com/nnnick/Chart.js
|
||||
// Definitions by: Alberto Nuti <https://github.com/anuti>
|
||||
// Fabien Lavocat <https://github.com/FabienLavocat>
|
||||
// KentarouTakeda <https://github.com/KentarouTakeda>
|
||||
// Larry Bahr <https://github.com/larrybahr>
|
||||
// Daniel Luz <https://github.com/mernen>
|
||||
// Joseph Page <https://github.com/josefpaij>
|
||||
// Dan Manastireanu <https://github.com/danmana>
|
||||
// Guillaume Rodriguez <https://github.com/guillaume-ro-fr>
|
||||
// Sergey Rubanov <https://github.com/chicoxyzzy>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
declare class Chart {
|
||||
static readonly Chart: typeof Chart;
|
||||
constructor(
|
||||
context: string | CanvasRenderingContext2D | HTMLCanvasElement | ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>,
|
||||
options: Chart.ChartConfiguration
|
||||
);
|
||||
config: Chart.ChartConfiguration;
|
||||
data: Chart.ChartData;
|
||||
destroy: () => {};
|
||||
update: (duration?: any, lazy?: any) => {};
|
||||
render: (duration?: any, lazy?: any) => {};
|
||||
stop: () => {};
|
||||
resize: () => {};
|
||||
clear: () => {};
|
||||
toBase64: () => string;
|
||||
generateLegend: () => {};
|
||||
getElementAtEvent: (e: any) => {};
|
||||
getElementsAtEvent: (e: any) => Array<{}>;
|
||||
getDatasetAtEvent: (e: any) => Array<{}>;
|
||||
ctx: CanvasRenderingContext2D|null;
|
||||
canvas: HTMLCanvasElement|null;
|
||||
chartArea: Chart.ChartArea;
|
||||
static pluginService: PluginServiceStatic;
|
||||
|
||||
static defaults: {
|
||||
global: Chart.ChartOptions & Chart.ChartFontOptions;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
static controllers: {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// Tooltip Static Options
|
||||
static Tooltip: Chart.ChartTooltipsStaticConfiguration;
|
||||
static helpers: HelperStatic;
|
||||
}
|
||||
declare class PluginServiceStatic {
|
||||
register(plugin: PluginServiceRegistrationOptions): void;
|
||||
unregister(plugin: PluginServiceRegistrationOptions): void;
|
||||
}
|
||||
declare class HelperStatic {
|
||||
color(color: string): Color;
|
||||
}
|
||||
declare class Color {
|
||||
alpha(alpha: number): Color;
|
||||
rgbString(): string
|
||||
}
|
||||
|
||||
interface PluginServiceRegistrationOptions {
|
||||
beforeInit?(chartInstance: Chart): void;
|
||||
afterInit?(chartInstance: Chart): void;
|
||||
|
||||
resize?(chartInstance: Chart, newChartSize: Size): void;
|
||||
|
||||
beforeUpdate?(chartInstance: Chart): void;
|
||||
afterScaleUpdate?(chartInstance: Chart): void;
|
||||
beforeDatasetsUpdate?(chartInstance: Chart): void;
|
||||
afterDatasetsUpdate?(chartInstance: Chart): void;
|
||||
afterUpdate?(chartInstance: Chart): void;
|
||||
|
||||
// This is called at the start of a render. It is only called once, even if the animation will run for a number of frames. Use beforeDraw or afterDraw
|
||||
// to do something on each animation frame
|
||||
beforeRender?(chartInstance: Chart): void;
|
||||
|
||||
// Easing is for animation
|
||||
beforeDraw?(chartInstance: Chart, easing: string): void;
|
||||
afterDraw?(chartInstance: Chart, easing: string): void;
|
||||
// Before the datasets are drawn but after scales are drawn
|
||||
beforeDatasetsDraw?(chartInstance: Chart, easing: string): void;
|
||||
afterDatasetsDraw?(chartInstance: Chart, easing: string): void;
|
||||
|
||||
// Called before drawing the `tooltip`. If any plugin returns `false`,
|
||||
// the tooltip drawing is cancelled until another `render` is triggered.
|
||||
beforeTooltipDraw?(chartInstance: Chart): void;
|
||||
// Called after drawing the `tooltip`. Note that this hook will not,
|
||||
// be called if the tooltip drawing has been previously cancelled.
|
||||
afterTooltipDraw?(chartInstance: Chart): void;
|
||||
|
||||
destroy?(chartInstance: Chart): void;
|
||||
|
||||
// Called when an event occurs on the chart
|
||||
beforeEvent?(chartInstance: Chart, event: Event): void;
|
||||
afterEvent?(chartInstance: Chart, event: Event): void;
|
||||
}
|
||||
|
||||
interface Size {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
declare namespace Chart {
|
||||
type ChartType = 'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie';
|
||||
|
||||
type TimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
||||
|
||||
type ScaleType = 'category' | 'linear' | 'logarithmic' | 'time' | 'radialLinear';
|
||||
|
||||
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
|
||||
|
||||
type PositionType = 'left' | 'right' | 'top' | 'bottom';
|
||||
|
||||
interface ChartArea {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
interface ChartLegendItem {
|
||||
text?: string;
|
||||
fillStyle?: string;
|
||||
hidden?: boolean;
|
||||
lineCap?: string;
|
||||
lineDash?: number[];
|
||||
lineDashOffset?: number;
|
||||
lineJoin?: string;
|
||||
lineWidth?: number;
|
||||
strokeStyle?: string;
|
||||
pointStyle?: PointStyle;
|
||||
}
|
||||
|
||||
interface ChartTooltipItem {
|
||||
xLabel?: string;
|
||||
yLabel?: string;
|
||||
datasetIndex?: number;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
interface ChartTooltipLabelColor {
|
||||
borderColor: ChartColor;
|
||||
backgroundColor: ChartColor;
|
||||
}
|
||||
|
||||
interface ChartTooltipCallback {
|
||||
beforeTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
title?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
afterTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
beforeBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
beforeLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
|
||||
label?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
|
||||
labelColor?(tooltipItem: ChartTooltipItem, chart: Chart): ChartTooltipLabelColor;
|
||||
labelTextColor?(tooltipItem: ChartTooltipItem, chart: Chart): string;
|
||||
afterLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
|
||||
afterBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
beforeFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
footer?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
afterFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
|
||||
}
|
||||
|
||||
interface ChartAnimationParameter {
|
||||
chartInstance?: any;
|
||||
animationObject?: any;
|
||||
}
|
||||
|
||||
interface ChartPoint {
|
||||
x?: number | string | Date;
|
||||
y?: number;
|
||||
r?: number;
|
||||
}
|
||||
|
||||
interface ChartConfiguration {
|
||||
type?: ChartType | string;
|
||||
data?: ChartData;
|
||||
options?: ChartOptions;
|
||||
// Plugins can require any options
|
||||
plugins?: any;
|
||||
}
|
||||
|
||||
interface ChartData {
|
||||
labels?: Array<string | string[]>;
|
||||
datasets?: ChartDataSets[];
|
||||
}
|
||||
|
||||
interface ChartOptions {
|
||||
responsive?: boolean;
|
||||
responsiveAnimationDuration?: number;
|
||||
aspectRatio?: number;
|
||||
maintainAspectRatio?: boolean;
|
||||
events?: string[];
|
||||
onClick?(event?: MouseEvent, activeElements?: Array<{}>): any;
|
||||
title?: ChartTitleOptions;
|
||||
legend?: ChartLegendOptions;
|
||||
tooltips?: ChartTooltipOptions;
|
||||
hover?: ChartHoverOptions;
|
||||
animation?: ChartAnimationOptions;
|
||||
elements?: ChartElementsOptions;
|
||||
layout?: ChartLayoutOptions;
|
||||
scales?: ChartScales;
|
||||
showLines?: boolean;
|
||||
spanGaps?: boolean;
|
||||
cutoutPercentage?: number;
|
||||
circumference?: number;
|
||||
rotation?: number;
|
||||
}
|
||||
|
||||
interface ChartFontOptions {
|
||||
defaultFontColor?: ChartColor;
|
||||
defaultFontFamily?: string;
|
||||
defaultFontSize?: number;
|
||||
defaultFontStyle?: string;
|
||||
}
|
||||
|
||||
interface ChartTitleOptions {
|
||||
display?: boolean;
|
||||
position?: PositionType;
|
||||
fullWdith?: boolean;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
fontColor?: ChartColor;
|
||||
fontStyle?: string;
|
||||
padding?: number;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface ChartLegendOptions {
|
||||
display?: boolean;
|
||||
position?: PositionType;
|
||||
fullWidth?: boolean;
|
||||
onClick?(event: MouseEvent, legendItem: ChartLegendItem): void;
|
||||
onHover?(event: MouseEvent, legendItem: ChartLegendItem): void;
|
||||
labels?: ChartLegendLabelOptions;
|
||||
reverse?: boolean;
|
||||
}
|
||||
|
||||
interface ChartLegendLabelOptions {
|
||||
boxWidth?: number;
|
||||
fontSize?: number;
|
||||
fontStyle?: string;
|
||||
fontColor?: ChartColor;
|
||||
fontFamily?: string;
|
||||
padding?: number;
|
||||
generateLabels?(chart: any): any;
|
||||
filter?(item: ChartLegendItem, data: ChartData): any;
|
||||
usePointStyle?: boolean;
|
||||
}
|
||||
|
||||
interface ChartTooltipOptions {
|
||||
enabled?: boolean;
|
||||
custom?(a: any): void;
|
||||
mode?: string;
|
||||
intersect?: boolean;
|
||||
backgroundColor?: ChartColor;
|
||||
titleFontFamily?: string;
|
||||
titleFontSize?: number;
|
||||
titleFontStyle?: string;
|
||||
titleFontColor?: ChartColor;
|
||||
titleSpacing?: number;
|
||||
titleMarginBottom?: number;
|
||||
bodyFontFamily?: string;
|
||||
bodyFontSize?: number;
|
||||
bodyFontStyle?: string;
|
||||
bodyFontColor?: ChartColor;
|
||||
bodySpacing?: number;
|
||||
footerFontFamily?: string;
|
||||
footerFontSize?: number;
|
||||
footerFontStyle?: string;
|
||||
footerFontColor?: ChartColor;
|
||||
footerSpacing?: number;
|
||||
footerMarginTop?: number;
|
||||
xPadding?: number;
|
||||
yPadding?: number;
|
||||
caretSize?: number;
|
||||
cornerRadius?: number;
|
||||
multiKeyBackground?: string;
|
||||
callbacks?: ChartTooltipCallback;
|
||||
filter?(item: ChartTooltipItem): boolean;
|
||||
itemSort?(itemA: ChartTooltipItem, itemB: ChartTooltipItem): number;
|
||||
position?: string;
|
||||
caretPadding?: number;
|
||||
displayColors?: boolean;
|
||||
borderColor?: ChartColor;
|
||||
borderWidth?: number;
|
||||
}
|
||||
|
||||
interface ChartTooltipsStaticConfiguration {
|
||||
positioners: {[mode: string]: ChartTooltipPositioner};
|
||||
}
|
||||
|
||||
type ChartTooltipPositioner = (elements: any[], eventPosition: Point) => Point;
|
||||
|
||||
interface ChartHoverOptions {
|
||||
mode?: string;
|
||||
animationDuration?: number;
|
||||
intersect?: boolean;
|
||||
onHover?(active: any): void;
|
||||
}
|
||||
|
||||
interface ChartAnimationObject {
|
||||
currentStep?: number;
|
||||
numSteps?: number;
|
||||
easing?: string;
|
||||
render?(arg: any): void;
|
||||
onAnimationProgress?(arg: any): void;
|
||||
onAnimationComplete?(arg: any): void;
|
||||
}
|
||||
|
||||
interface ChartAnimationOptions {
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
onProgress?(chart: any): void;
|
||||
onComplete?(chart: any): void;
|
||||
}
|
||||
|
||||
interface ChartElementsOptions {
|
||||
point?: ChartPointOptions;
|
||||
line?: ChartLineOptions;
|
||||
arc?: ChartArcOptions;
|
||||
rectangle?: ChartRectangleOptions;
|
||||
}
|
||||
|
||||
interface ChartArcOptions {
|
||||
backgroundColor?: ChartColor;
|
||||
borderColor?: ChartColor;
|
||||
borderWidth?: number;
|
||||
}
|
||||
|
||||
interface ChartLineOptions {
|
||||
tension?: number;
|
||||
backgroundColor?: ChartColor;
|
||||
borderWidth?: number;
|
||||
borderColor?: ChartColor;
|
||||
borderCapStyle?: string;
|
||||
borderDash?: any[];
|
||||
borderDashOffset?: number;
|
||||
borderJoinStyle?: string;
|
||||
capBezierPoints?: boolean;
|
||||
fill?: 'zero' | 'top' | 'bottom' | boolean;
|
||||
stepped?: boolean;
|
||||
}
|
||||
|
||||
interface ChartPointOptions {
|
||||
radius?: number;
|
||||
pointStyle?: PointStyle;
|
||||
backgroundColor?: ChartColor;
|
||||
borderWidth?: number;
|
||||
borderColor?: ChartColor;
|
||||
hitRadius?: number;
|
||||
hoverRadius?: number;
|
||||
hoverBorderWidth?: number;
|
||||
}
|
||||
|
||||
interface ChartRectangleOptions {
|
||||
backgroundColor?: ChartColor;
|
||||
borderWidth?: number;
|
||||
borderColor?: ChartColor;
|
||||
borderSkipped?: string;
|
||||
}
|
||||
|
||||
interface ChartLayoutOptions {
|
||||
padding?: ChartLayoutPaddingObject | number;
|
||||
}
|
||||
|
||||
interface ChartLayoutPaddingObject {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
}
|
||||
|
||||
interface GridLineOptions {
|
||||
display?: boolean;
|
||||
color?: ChartColor;
|
||||
borderDash?: number[];
|
||||
borderDashOffset?: number;
|
||||
lineWidth?: number;
|
||||
drawBorder?: boolean;
|
||||
drawOnChartArea?: boolean;
|
||||
drawTicks?: boolean;
|
||||
tickMarkLength?: number;
|
||||
zeroLineWidth?: number;
|
||||
zeroLineColor?: ChartColor;
|
||||
zeroLineBorderDash?: number[];
|
||||
zeroLineBorderDashOffset?: number;
|
||||
offsetGridLines?: boolean;
|
||||
}
|
||||
|
||||
interface ScaleTitleOptions {
|
||||
display?: boolean;
|
||||
labelString?: string;
|
||||
fontColor?: ChartColor;
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
fontStyle?: string;
|
||||
}
|
||||
|
||||
interface TickOptions {
|
||||
autoSkip?: boolean;
|
||||
autoSkipPadding?: number;
|
||||
callback?(value: any, index: any, values: any): string|number;
|
||||
display?: boolean;
|
||||
fontColor?: ChartColor;
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
fontStyle?: string;
|
||||
labelOffset?: number;
|
||||
maxRotation?: number;
|
||||
minRotation?: number;
|
||||
mirror?: boolean;
|
||||
padding?: number;
|
||||
reverse?: boolean;
|
||||
min?: any;
|
||||
max?: any;
|
||||
}
|
||||
interface AngleLineOptions {
|
||||
display?: boolean;
|
||||
color?: ChartColor;
|
||||
lineWidth?: number;
|
||||
}
|
||||
|
||||
interface PointLabelOptions {
|
||||
callback?(arg: any): any;
|
||||
fontColor?: ChartColor;
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
fontStyle?: string;
|
||||
}
|
||||
|
||||
interface TickOptions {
|
||||
backdropColor?: ChartColor;
|
||||
backdropPaddingX?: number;
|
||||
backdropPaddingY?: number;
|
||||
maxTicksLimit?: number;
|
||||
showLabelBackdrop?: boolean;
|
||||
}
|
||||
interface LinearTickOptions extends TickOptions {
|
||||
beginAtZero?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
maxTicksLimit?: number;
|
||||
stepSize?: number;
|
||||
suggestedMin?: number;
|
||||
suggestedMax?: number;
|
||||
}
|
||||
|
||||
interface LogarithmicTickOptions extends TickOptions {
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
type ChartColor = string | CanvasGradient | CanvasPattern | string[];
|
||||
|
||||
interface ChartDataSets {
|
||||
cubicInterpolationMode?: 'default' | 'monotone';
|
||||
backgroundColor?: ChartColor | ChartColor[];
|
||||
borderWidth?: number | number[];
|
||||
borderColor?: ChartColor | ChartColor[];
|
||||
borderCapStyle?: string;
|
||||
borderDash?: number[];
|
||||
borderDashOffset?: number;
|
||||
borderJoinStyle?: string;
|
||||
borderSkipped?: PositionType;
|
||||
data?: number[] | ChartPoint[];
|
||||
fill?: boolean | number | string;
|
||||
hoverBackgroundColor?: string | string[];
|
||||
hoverBorderColor?: string | string[];
|
||||
hoverBorderWidth?: number | number[];
|
||||
label?: string;
|
||||
lineTension?: number;
|
||||
steppedLine?: 'before' | 'after' | boolean;
|
||||
pointBorderColor?: ChartColor | ChartColor[];
|
||||
pointBackgroundColor?: ChartColor | ChartColor[];
|
||||
pointBorderWidth?: number | number[];
|
||||
pointRadius?: number | number[];
|
||||
pointHoverRadius?: number | number[];
|
||||
pointHitRadius?: number | number[];
|
||||
pointHoverBackgroundColor?: ChartColor | ChartColor[];
|
||||
pointHoverBorderColor?: ChartColor | ChartColor[];
|
||||
pointHoverBorderWidth?: number | number[];
|
||||
pointStyle?: PointStyle | HTMLImageElement | Array<PointStyle | HTMLImageElement>;
|
||||
xAxisID?: string;
|
||||
yAxisID?: string;
|
||||
type?: string;
|
||||
hidden?: boolean;
|
||||
hideInLegendAndTooltip?: boolean;
|
||||
showLine?: boolean;
|
||||
stack?: string;
|
||||
spanGaps?: boolean;
|
||||
}
|
||||
|
||||
interface ChartScales {
|
||||
type?: ScaleType | string;
|
||||
display?: boolean;
|
||||
position?: PositionType | string;
|
||||
gridLines?: GridLineOptions;
|
||||
scaleLabel?: ScaleTitleOptions;
|
||||
ticks?: TickOptions;
|
||||
xAxes?: ChartXAxe[];
|
||||
yAxes?: ChartYAxe[];
|
||||
}
|
||||
|
||||
interface CommonAxe {
|
||||
type?: ScaleType | string;
|
||||
display?: boolean;
|
||||
id?: string;
|
||||
stacked?: boolean;
|
||||
position?: string;
|
||||
ticks?: TickOptions;
|
||||
gridLines?: GridLineOptions;
|
||||
barThickness?: number;
|
||||
scaleLabel?: ScaleTitleOptions;
|
||||
beforeUpdate?(scale?: any): void;
|
||||
beforeSetDimension?(scale?: any): void;
|
||||
beforeDataLimits?(scale?: any): void;
|
||||
beforeBuildTicks?(scale?: any): void;
|
||||
beforeTickToLabelConversion?(scale?: any): void;
|
||||
beforeCalculateTickRotation?(scale?: any): void;
|
||||
beforeFit?(scale?: any): void;
|
||||
afterUpdate?(scale?: any): void;
|
||||
afterSetDimension?(scale?: any): void;
|
||||
afterDataLimits?(scale?: any): void;
|
||||
afterBuildTicks?(scale?: any): void;
|
||||
afterTickToLabelConversion?(scale?: any): void;
|
||||
afterCalculateTickRotation?(scale?: any): void;
|
||||
afterFit?(scale?: any): void;
|
||||
}
|
||||
|
||||
interface ChartXAxe extends CommonAxe {
|
||||
categoryPercentage?: number;
|
||||
barPercentage?: number;
|
||||
time?: TimeScale;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-empty-interface
|
||||
interface ChartYAxe extends CommonAxe {
|
||||
}
|
||||
|
||||
interface LinearScale extends ChartScales {
|
||||
ticks?: LinearTickOptions;
|
||||
}
|
||||
|
||||
interface LogarithmicScale extends ChartScales {
|
||||
ticks?: LogarithmicTickOptions;
|
||||
}
|
||||
|
||||
interface TimeDisplayFormat {
|
||||
millisecond?: string;
|
||||
second?: string;
|
||||
minute?: string;
|
||||
hour?: string;
|
||||
day?: string;
|
||||
week?: string;
|
||||
month?: string;
|
||||
quarter?: string;
|
||||
year?: string;
|
||||
}
|
||||
|
||||
interface TimeScale extends ChartScales {
|
||||
displayFormats?: TimeDisplayFormat;
|
||||
isoWeekday?: boolean;
|
||||
max?: string;
|
||||
min?: string;
|
||||
parser?: string | ((arg: any) => any);
|
||||
round?: TimeUnit;
|
||||
tooltipFormat?: string;
|
||||
unit?: TimeUnit;
|
||||
unitStepSize?: number;
|
||||
stepSize?: number;
|
||||
minUnit?: TimeUnit;
|
||||
}
|
||||
|
||||
interface RadialLinearScale {
|
||||
lineArc?: boolean;
|
||||
angleLines?: AngleLineOptions;
|
||||
pointLabels?: PointLabelOptions;
|
||||
ticks?: TickOptions;
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
}
|
||||
|
||||
export = Chart;
|
||||
export as namespace Chart;
|
@ -20,7 +20,7 @@ var mgr = &manager{}
|
||||
type manager struct {
|
||||
client *client.Client
|
||||
locker sync.Mutex
|
||||
logger *log.Logger
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (m *manager) Do(fn func(ctx context.Context, cli *client.Client) error) (err error) {
|
||||
@ -51,7 +51,7 @@ func (m *manager) Client() (ctx context.Context, cli *client.Client, err error)
|
||||
return context.TODO(), m.client, nil
|
||||
}
|
||||
|
||||
func (m *manager) Logger() *log.Logger {
|
||||
func (m *manager) Logger() log.Logger {
|
||||
if m.logger == nil {
|
||||
m.locker.Lock()
|
||||
defer m.locker.Unlock()
|
||||
|
@ -77,6 +77,7 @@ menu.raw: Raw
|
||||
menu.edit: Edit
|
||||
menu.log: Logs
|
||||
menu.perm: Permission
|
||||
menu.stats: Stats
|
||||
|
||||
# login page
|
||||
login.title: Sign in to Swirl
|
||||
|
@ -77,6 +77,7 @@ menu.raw: 原始
|
||||
menu.edit: 编辑
|
||||
menu.log: 日志
|
||||
menu.perm: 权限
|
||||
menu.stats: 状态
|
||||
|
||||
# login page
|
||||
login.title: 登录到 Swirl
|
||||
|
@ -1,17 +1,25 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/data/set"
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/ext/times"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/util/cast"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/biz/docker"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
"github.com/prometheus/client_golang/api"
|
||||
prometheus "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
pm "github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// ServiceController is a controller of docker service
|
||||
@ -29,6 +37,8 @@ type ServiceController struct {
|
||||
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"service.update" authorize:"!" perm:"write,service,name"`
|
||||
PermEdit web.HandlerFunc `path:"/:name/perm" name:"service.perm.edit" authorize:"!" perm:"write,service,name"`
|
||||
PermUpdate web.HandlerFunc `path:"/:name/perm" method:"post" name:"service.perm.update" authorize:"!" perm:"write,service,name"`
|
||||
Stats web.HandlerFunc `path:"/:name/stats" name:"service.stats" authorize:"!" perm:"read,service,name"`
|
||||
Metrics web.HandlerFunc `path:"/:name/metrics" name:"service.metrics" authorize:"?"`
|
||||
}
|
||||
|
||||
// Service creates an instance of ServiceController
|
||||
@ -47,6 +57,8 @@ func Service() (c *ServiceController) {
|
||||
Rollback: serviceRollback,
|
||||
PermEdit: servicePermEdit,
|
||||
PermUpdate: permUpdate("service", "name"),
|
||||
Stats: serviceStats,
|
||||
Metrics: serviceMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,3 +281,130 @@ func servicePermEdit(ctx web.Context) error {
|
||||
m := newModel(ctx).Set("Name", name)
|
||||
return permEdit(ctx, "service", name, "service/perm", m)
|
||||
}
|
||||
|
||||
func serviceStats(ctx web.Context) error {
|
||||
name := ctx.P("name")
|
||||
service, _, err := docker.ServiceInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, _, err := docker.TaskList(&model.TaskListArgs{Service: name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setting, err := biz.Setting.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||
refresh := cast.ToBool(ctx.Q("refresh"), true)
|
||||
m := newModel(ctx).Set("Service", service).Set("Tasks", tasks).
|
||||
Set("Time", period.String()).Set("Refresh", refresh).Set("Metrics", setting.Metrics.Prometheus != "")
|
||||
return ctx.Render("service/stats", m)
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func serviceMetrics(ctx web.Context) error {
|
||||
type chartPoint struct {
|
||||
X int64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
type chartDataset struct {
|
||||
Label string `json:"label"`
|
||||
Data []chartPoint `json:"data"`
|
||||
}
|
||||
|
||||
name := ctx.P("name")
|
||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||
var step time.Duration
|
||||
if period >= times.Day {
|
||||
step = 10 * time.Minute
|
||||
} else if period >= 12*time.Hour {
|
||||
step = 5 * time.Minute
|
||||
} else if period >= 6*time.Hour {
|
||||
step = 3 * time.Minute
|
||||
} else if period >= 3*time.Hour {
|
||||
step = 2 * time.Minute
|
||||
} else {
|
||||
step = time.Minute
|
||||
}
|
||||
|
||||
setting, err := biz.Setting.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := api.NewClient(api.Config{Address: setting.Metrics.Prometheus})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
papi := prometheus.NewAPI(client)
|
||||
|
||||
// cpu
|
||||
query := fmt.Sprintf(`rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`, name)
|
||||
end := time.Now()
|
||||
start := end.Add(-period)
|
||||
value, err := papi.QueryRange(context.Background(), query, prometheus.Range{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matrix := value.(pm.Matrix)
|
||||
var cpuDatas []chartDataset
|
||||
for _, stream := range matrix {
|
||||
ds := chartDataset{
|
||||
Label: string(stream.Metric["name"]),
|
||||
}
|
||||
for _, v := range stream.Values {
|
||||
p := chartPoint{
|
||||
X: int64(v.Timestamp),
|
||||
Y: float64(v.Value),
|
||||
}
|
||||
ds.Data = append(ds.Data, p)
|
||||
}
|
||||
cpuDatas = append(cpuDatas, ds)
|
||||
}
|
||||
|
||||
// memory
|
||||
query = fmt.Sprintf(`container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`, name)
|
||||
value, err = papi.QueryRange(context.Background(), query, prometheus.Range{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matrix = value.(pm.Matrix)
|
||||
var memoryDatas []chartDataset
|
||||
for _, stream := range matrix {
|
||||
ds := chartDataset{
|
||||
Label: string(stream.Metric["name"]),
|
||||
}
|
||||
for _, v := range stream.Values {
|
||||
p := chartPoint{
|
||||
X: int64(v.Timestamp),
|
||||
Y: float64(v.Value) / 1024 / 1024,
|
||||
}
|
||||
ds.Data = append(ds.Data, p)
|
||||
}
|
||||
memoryDatas = append(memoryDatas, ds)
|
||||
}
|
||||
|
||||
// start time
|
||||
//query = fmt.Sprintf(`container_start_time_seconds{container_label_com_docker_swarm_service_name="%s"}`, name)
|
||||
//value, err = papi.Query(context.Background(), query, end)
|
||||
//scalar := value.(*pm.Scalar)
|
||||
|
||||
m := data.Map{
|
||||
"cpu": cpuDatas,
|
||||
"memory": memoryDatas,
|
||||
}
|
||||
return ctx.JSON(m)
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ func (d *database) Run(cmd, result interface{}) error {
|
||||
}
|
||||
|
||||
type Dao struct {
|
||||
logger *log.Logger
|
||||
logger log.Logger
|
||||
session *mgo.Session
|
||||
}
|
||||
|
||||
|
2
main.go
2
main.go
@ -26,7 +26,7 @@ func main() {
|
||||
misc.BindOptions()
|
||||
|
||||
app.Name = "Swirl"
|
||||
app.Version = "0.6.8"
|
||||
app.Version = "0.6.9"
|
||||
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||
app.Action = func(ctx *app.Context) {
|
||||
misc.LoadOptions()
|
||||
|
@ -43,7 +43,10 @@ type Setting struct {
|
||||
Name string `bson:"name" json:"name,omitempty"` // Asia/Shanghai
|
||||
Offset int32 `bson:"offset" json:"offset,omitempty"` // seconds east of UTC
|
||||
} `bson:"tz" json:"tz,omitempty"`
|
||||
Language string `bson:"lang" json:"lang,omitempty"`
|
||||
Language string `bson:"lang" json:"lang,omitempty"`
|
||||
Metrics struct {
|
||||
Prometheus string `bson:"prometheus" json:"prometheus"`
|
||||
} `bson:"metrics" json:"metrics"`
|
||||
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
||||
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ var Perms = []PermGroup{
|
||||
{Key: "service.detail", Text: "View detail"},
|
||||
{Key: "service.raw", Text: "View raw"},
|
||||
{Key: "service.logs", Text: "View logs"},
|
||||
{Key: "service.stats", Text: "View stats"},
|
||||
{Key: "service.edit", Text: "View edit"},
|
||||
{Key: "service.create", Text: "Create"},
|
||||
{Key: "service.delete", Text: "Delete"},
|
||||
|
@ -30,6 +30,7 @@
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -30,6 +30,7 @@
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -29,6 +29,7 @@
|
||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -30,6 +30,7 @@
|
||||
<a class="navbar-item is-tab" href="/service/{{.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab is-active" href="/service/{{.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Name}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -38,6 +38,7 @@
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
108
views/service/stats.jet
Normal file
108
views/service/stats.jet
Normal file
@ -0,0 +1,108 @@
|
||||
{{ extends "_base" }}
|
||||
{{ import "../_modules/detail" }}
|
||||
{{ import "../_modules/form" }}
|
||||
|
||||
{{ block script() }}
|
||||
<script src="/assets/chart/chart.bundle.min.js?v=2.7.2"></script>
|
||||
<script>$(() => new Swirl.Service.StatsPage())</script>
|
||||
{{ end }}
|
||||
|
||||
{{ block body_content() }}
|
||||
<div class="container">
|
||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||
<li><a href="/service/">{{ i18n("menu.service") }}</a></li>
|
||||
<li class="is-active"><a>{{ i18n("menu.stats") }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="hero is-small is-light">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h2 class="title is-2">
|
||||
{{ .Service.Spec.Name }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<nav class="navbar has-shadow">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/detail">{{ i18n("menu.detail") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service.Spec.Name}}/stats">{{ i18n("menu.stats") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{{ if .Metrics }}
|
||||
<nav class="level">
|
||||
<form>
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<a class="button is-static">Time</a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<div class="select">
|
||||
<select id="cb-time" name="time">
|
||||
{{ yield option(value="30m", label="Last 30 minutes", selected=.Time) }}
|
||||
{{ yield option(value="1h", label="Last 1 hour", selected=.Time) }}
|
||||
{{ yield option(value="3h", label="Last 3 hours", selected=.Time) }}
|
||||
{{ yield option(value="6h", label="Last 6 hours", selected=.Time) }}
|
||||
{{ yield option(value="12h", label="Last 12 hours", selected=.Time) }}
|
||||
{{ yield option(value="24h", label="Last 24 hours", selected=.Time) }}
|
||||
</select>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<div class="field">
|
||||
<input id="cb-refresh" name="refresh" value="true" type="checkbox" class="switch is-success is-rounded"{{if .Refresh}} checked{{end}}>
|
||||
<label for="cb-refresh">Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</nav>
|
||||
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
<p>CPU</p>
|
||||
</div>
|
||||
<div class="block-body is-bordered">
|
||||
<canvas id="canvas-cpu"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
<p>Memory</p>
|
||||
</div>
|
||||
<div class="block-body is-bordered">
|
||||
<canvas id="canvas-memory"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="notification is-info">
|
||||
NOTICE: To enable this feature, you must set <b>Metrics</b> option on <a href="/system/setting/">Setting</a> page first.
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<a href="/service/" class="button is-primary">
|
||||
<span class="icon"><i class="fas fa-reply"></i></span>
|
||||
<span>{{ i18n("button.return") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
@ -219,12 +219,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset id="fs-metrics">
|
||||
<legend class="lead is-5 is-bordered">Metrics</legend>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">Prometheus address</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input name="metrics.prometheus" value="{{ .Setting.Metrics.Prometheus }}" class="input" type="text" placeholder="e.g. http://prometheus.xxx.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<hr>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<button class="button is-primary" type="submit">{{ i18n("button.save") }}</button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="notification is-info">
|
||||
NOTICE: To activate modifications, you must restart swirl.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
Loading…
Reference in New Issue
Block a user