Skip to content

Commit 6667519

Browse files
feat: add Host Usage charts and side-by-side dashboard layout to quadra view
Move VPU Usage and Host Usage charts to top of page in a 50/50 split layout matching the NetInt dashboard reference. VPU Usage (left) shows combined resource data across all cards with Memory Usage and Load Average bars. Host Usage (right) shows CPU, RAM, and storage with right-aligned info labels (thread count, total RAM, total disk). Add Roboto font scoped to the quadra view via view-specific CSS. Rename Load to Load Average, swap series order so Memory Usage renders first (top bar), and use #38c5eb for all cyan bars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 15d73b7 commit 6667519

File tree

4 files changed

+317
-102
lines changed

4 files changed

+317
-102
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2+
3+
/* Quadra view uses Roboto to match the NetInt dashboard style */
4+
#page.quadra-page {
5+
font-family: 'Roboto', 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
6+
}
7+
8+
#page.quadra-page .card-header {
9+
font-weight: 500;
10+
font-size: 14px;
11+
}
12+
13+
#page.quadra-page .card-header strong {
14+
font-weight: 700;
15+
}
16+
17+
#page.quadra-page h6 {
18+
font-weight: 500;
19+
font-size: 13px;
20+
margin-bottom: 8px;
21+
}
22+
23+
#page.quadra-page .table {
24+
font-size: 12px;
25+
}
26+
27+
#page.quadra-page .table th {
28+
font-weight: 500;
29+
}

web/skins/classic/views/js/quadra.js

Lines changed: 187 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,200 @@
11
function initCharts() {
2-
if (typeof echarts === 'undefined' || typeof quadraChartData === 'undefined') return;
2+
if (typeof echarts === 'undefined') return;
33

4-
quadraChartData.forEach(function(cardData) {
5-
var el = document.getElementById(cardData.id);
6-
if (!el) return;
4+
// Combined VPU Usage chart (all cards)
5+
if (typeof quadraVpuData !== 'undefined' && quadraVpuData.length > 0) {
6+
var el = document.getElementById('chart-vpu-usage');
7+
if (el) {
8+
var vpuChart = echarts.init(el);
79

8-
var chart = echarts.init(el);
10+
var categories = [];
11+
var loadData = [];
12+
var memData = [];
13+
for (var i = quadraVpuData.length - 1; i >= 0; i--) {
14+
var r = quadraVpuData[i];
15+
categories.push(r.label);
16+
loadData.push(r.load);
17+
memData.push(r.mem);
18+
}
919

10-
// Build category labels and data arrays (reversed so first item appears at top)
11-
var categories = [];
12-
var loadData = [];
13-
var memData = [];
14-
for (var i = cardData.resources.length - 1; i >= 0; i--) {
15-
var r = cardData.resources[i];
16-
categories.push(r.label);
17-
loadData.push(r.load);
18-
memData.push(r.mem);
20+
vpuChart.setOption({
21+
tooltip: {
22+
trigger: 'axis',
23+
axisPointer: {type: 'shadow'},
24+
formatter: function(params) {
25+
var label = params[0].name;
26+
var lines = [label];
27+
params.forEach(function(p) {
28+
lines.push(p.marker + ' ' + p.seriesName + ': ' + p.value + '%');
29+
});
30+
return lines.join('<br/>');
31+
}
32+
},
33+
legend: {
34+
data: ['Memory Usage', 'Load Average'],
35+
top: 0,
36+
textStyle: {fontSize: 11}
37+
},
38+
grid: {
39+
left: (typeof quadraMultiCard !== 'undefined' && quadraMultiCard) ? 120 : 80,
40+
right: 40,
41+
top: 30,
42+
bottom: 25
43+
},
44+
xAxis: {
45+
type: 'value',
46+
min: 0,
47+
max: 100,
48+
axisLabel: {formatter: '{value}%', fontSize: 10},
49+
splitLine: {lineStyle: {type: 'dashed', color: '#e0e0e0'}}
50+
},
51+
yAxis: {
52+
type: 'category',
53+
data: categories,
54+
axisLabel: {fontSize: 11},
55+
axisTick: {show: false}
56+
},
57+
series: [
58+
{
59+
name: 'Memory Usage',
60+
type: 'bar',
61+
data: memData,
62+
barWidth: 8,
63+
itemStyle: {color: '#38c5eb', borderRadius: [0, 3, 3, 0]},
64+
label: {
65+
show: true,
66+
position: 'right',
67+
fontSize: 10,
68+
formatter: '{c}%'
69+
}
70+
},
71+
{
72+
name: 'Load Average',
73+
type: 'bar',
74+
data: loadData,
75+
barWidth: 8,
76+
itemStyle: {color: '#8e8e8e', borderRadius: [0, 3, 3, 0]},
77+
label: {
78+
show: true,
79+
position: 'right',
80+
fontSize: 10,
81+
formatter: '{c}%'
82+
}
83+
}
84+
]
85+
});
86+
87+
window.addEventListener('resize', function() {
88+
vpuChart.resize();
89+
});
1990
}
91+
}
2092

21-
chart.setOption({
22-
tooltip: {
23-
trigger: 'axis',
24-
axisPointer: {type: 'shadow'},
25-
formatter: function(params) {
26-
var label = params[0].name;
27-
var lines = [label];
28-
params.forEach(function(p) {
29-
lines.push(p.marker + ' ' + p.seriesName + ': ' + p.value + '%');
30-
});
31-
return lines.join('<br/>');
32-
}
33-
},
34-
legend: {
35-
data: ['Load', 'Memory Usage'],
36-
top: 0,
37-
textStyle: {fontSize: 11}
38-
},
39-
grid: {
40-
left: 70,
41-
right: 30,
42-
top: 30,
43-
bottom: 25
44-
},
45-
xAxis: {
46-
type: 'value',
47-
min: 0,
48-
max: 100,
49-
axisLabel: {formatter: '{value}%', fontSize: 10},
50-
splitLine: {lineStyle: {type: 'dashed', color: '#e0e0e0'}}
51-
},
52-
yAxis: {
53-
type: 'category',
54-
data: categories,
55-
axisLabel: {fontSize: 11},
56-
axisTick: {show: false}
57-
},
58-
series: [
59-
{
60-
name: 'Load',
61-
type: 'bar',
62-
data: loadData,
63-
barWidth: 8,
64-
itemStyle: {color: '#8e8e8e', borderRadius: [0, 3, 3, 0]},
65-
label: {
66-
show: true,
67-
position: 'right',
68-
fontSize: 10,
69-
formatter: '{c}%'
93+
// Host Usage chart
94+
if (typeof quadraHostUsage !== 'undefined' && quadraHostUsage.length > 0) {
95+
var hostEl = document.getElementById('chart-host-usage');
96+
if (hostEl) {
97+
var hostChart = echarts.init(hostEl);
98+
99+
var hostCategories = [];
100+
var hostValues = [];
101+
var infoLabels = [];
102+
for (var i = quadraHostUsage.length - 1; i >= 0; i--) {
103+
var h = quadraHostUsage[i];
104+
hostCategories.push(h.label);
105+
hostValues.push(h.value);
106+
infoLabels.push(h.info);
107+
}
108+
109+
hostChart.setOption({
110+
tooltip: {
111+
trigger: 'axis',
112+
axisPointer: {type: 'shadow'},
113+
formatter: function(params) {
114+
var p = params[0];
115+
var idx = p.dataIndex;
116+
return p.name + ': ' + p.value + '% (' + infoLabels[idx] + ')';
70117
}
71118
},
72-
{
73-
name: 'Memory Usage',
74-
type: 'bar',
75-
data: memData,
76-
barWidth: 8,
77-
itemStyle: {color: '#00bcd4', borderRadius: [0, 3, 3, 0]},
78-
label: {
79-
show: true,
80-
position: 'right',
81-
fontSize: 10,
82-
formatter: '{c}%'
119+
grid: {
120+
left: 80,
121+
right: 80,
122+
top: 10,
123+
bottom: 25
124+
},
125+
xAxis: {
126+
type: 'value',
127+
min: 0,
128+
max: 100,
129+
axisLabel: {formatter: '{value}%', fontSize: 10},
130+
splitLine: {lineStyle: {type: 'dashed', color: '#e0e0e0'}}
131+
},
132+
yAxis: {
133+
type: 'category',
134+
data: hostCategories,
135+
axisLabel: {fontSize: 12, fontWeight: 'bold'},
136+
axisTick: {show: false}
137+
},
138+
series: [
139+
{
140+
type: 'bar',
141+
data: hostValues.map(function(v) {
142+
return {
143+
value: v,
144+
label: {
145+
show: true,
146+
position: v > 15 ? 'insideLeft' : 'right',
147+
fontSize: 10,
148+
color: v > 15 ? '#fff' : '#333',
149+
formatter: v + '%'
150+
}
151+
};
152+
}),
153+
barWidth: 12,
154+
itemStyle: {color: '#38c5eb', borderRadius: [0, 3, 3, 0]}
83155
}
84-
}
85-
]
86-
});
156+
]
157+
});
87158

88-
window.addEventListener('resize', function() {
89-
chart.resize();
90-
});
91-
});
159+
// Add right-aligned info text using graphic elements
160+
var graphicElements = [];
161+
for (var gi = 0; gi < infoLabels.length; gi++) {
162+
graphicElements.push({
163+
type: 'text',
164+
right: 10,
165+
top: hostChart.convertToPixel('grid', [0, gi])[1] - 7,
166+
style: {
167+
text: infoLabels[gi],
168+
fontSize: 11,
169+
fill: '#666',
170+
textAlign: 'right'
171+
},
172+
silent: true
173+
});
174+
}
175+
hostChart.setOption({graphic: graphicElements});
176+
177+
window.addEventListener('resize', function() {
178+
hostChart.resize();
179+
var elems = [];
180+
for (var ri = 0; ri < infoLabels.length; ri++) {
181+
elems.push({
182+
type: 'text',
183+
right: 10,
184+
top: hostChart.convertToPixel('grid', [0, ri])[1] - 7,
185+
style: {
186+
text: infoLabels[ri],
187+
fontSize: 11,
188+
fill: '#666',
189+
textAlign: 'right'
190+
},
191+
silent: true
192+
});
193+
}
194+
hostChart.setOption({graphic: elems});
195+
});
196+
}
197+
}
92198
}
93199

94200
function initPage() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
var quadraRefreshInterval = <?php echo 1000*ZM_WEB_REFRESH_MAIN ?>;
2+
var quadraMultiCard = <?php echo (isset($multiCard) && $multiCard) ? 'true' : 'false' ?>;

0 commit comments

Comments
 (0)