<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>sysinfo-web</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/simplex/bootstrap.min.css" type="text/css" media="screen"/>
<link rel="icon" href="favicon.ico" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-chartjs/2.6.2/vue-chartjs.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<style type="text/css" media="screen">
body {
background-color: #F9F9F9;
}
a {
color: #FE4365;
}
h2 {
font-size: 1.2em;
text-align: center;
}
div.sort {
cursor: pointer;
}
hr.global-cpu-divider {
margin-top: 0;
}
.nav-tabs {
border-bottom: none;
}
.nav-tabs li.active a {
background-color: #fff;
}
.cpu-usage-panel {
border-top-left-radius: 0;
}
div.memory-pie-chart-container {
padding: 0 40px;
}
.progress-bar-ours {
background-color: #69D2E7;
}
</style>
</head>
<body>
<div id="sysinfo" class="container" v-if="sysinfo">
<h2>{{ sysinfo.hostname }} — {{ sysinfo.uptime }}</h2>
<hr/>
<div class="row">
<div class="col-md-6">
<ul class="nav nav-tabs">
<li class="active"><a href="#cpu_usage_bars" data-toggle="tab">CPU Usage</a></li>
<li><a href="#cpu_usage_graph" data-toggle="tab">History</a></li>
</ul>
<div class="panel panel-default cpu-usage-panel">
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade active in" id="cpu_usage_bars">
<div class="row">
<div class="col-xs-2">Global</div>
<div class="col-xs-10">
<div class="progress">
<div class="progress-bar progress-bar-ours" :style="'width: ' + (sysinfo.processor_list[0].cpu_usage * 100) + '%'"></div>
</div>
</div>
</div>
<hr class="global-cpu-divider">
<div class="row" v-for="processor in sysinfo.processor_list.slice(1)">
<div class="col-xs-2">{{ processor.name }}</div>
<div class="col-xs-10">
<div class="progress">
<div class="progress-bar progress-bar-ours" :style="'width: ' + (processor.cpu_usage * 100) + '%'"></div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="cpu_usage_graph">
<line-chart :chart-data="processorUsageHistory.chartData" :options="{scales: {yAxes: [{ ticks: { max: 100, min: 0 }, scaleLabel: { display: true, labelString: 'CPU usage (%)' }}]}}"></line-chart>
</div>
</div>
</div>
</div>
<ul class="nav nav-tabs">
<li class="active"><a href="#temperature_bars" data-toggle="tab">Temperature</a></li>
<li><a href="#temperature_graph" data-toggle="tab">History</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade active in" id="temperature_bars">
<table class="table table-striped table-hover">
<thead>
<tr>
<td>Label</td>
<td>Temperature</td>
<td>Max</td>
<td>Critical</td>
</tr>
</thead>
<tbody>
<tr v-for="component in sysinfo.components_list">
<td>{{ component.label }}</td>
<td>{{ component.temperature }} °C</td>
<td>{{ component.max }} °C</td>
<td v-if="component.critical && component.critical != ''">{{ component.critical }} °C</td>
<td v-else></td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="temperature_graph">
<line-chart :chart-data="temperatureHistory.chartData" :options="{scales: {yAxes: [{ scaleLabel: { display: true, labelString: 'Temperature (°C)' }}]}}"></line-chart>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<td>Name</td>
<td>MP</td>
<td>FS</td>
<td>Size</td>
<td>Used</td>
<td>Avail</td>
</tr>
</thead>
<tbody>
<tr v-for="disk in sysinfo.disks">
<td>{{ disk.name }}</td>
<td>{{ disk.mount_point }}</td>
<td>{{ disk.file_system }}</td>
<td>{{ disk.total_space | readableBytes }}</td>
<td>{{ disk.total_space - disk.available_space | readableBytes }}</td>
<td>{{ disk.available_space | readableBytes }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<ul class="nav nav-tabs">
<li class="active"><a href="#memory_usage_bars" data-toggle="tab">Memory Usage</a></li>
<li><a href="#memory_usage_graph" data-toggle="tab">History</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade active in" id="memory_usage_bars">
<div class="row">
<div class="col-xs-6 memory-pie-chart-container">
<doughnut-chart :chart-data="memoryUsageChartData" :options="{ title: { display: true, text: 'Memory' }, legend: { display: false } }"></doughnut-chart>
</div>
<div class="col-xs-6 memory-pie-chart-container">
<doughnut-chart :chart-data="swapUsageChartData" :options="{ title: { display: true, text: 'Swap' }, legend: { display: false } }"></doughnut-chart>
</div>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<td></td>
<td>Total</td>
<td>Used</td>
<td>Free</td>
</tr>
</thead>
<tbody>
<tr>
<td>Memory</td>
<td>{{ sysinfo.memory[0] | readableKbytes }}</td>
<td>{{ sysinfo.memory[1] | readableKbytes }}</td>
<td>{{ sysinfo.memory[2] | readableKbytes }}</td>
</tr>
<tr>
<td>Swap</td>
<td>{{ sysinfo.memory[3] | readableKbytes }}</td>
<td>{{ sysinfo.memory[4] | readableKbytes }}</td>
<td>{{ sysinfo.memory[5] | readableKbytes }}</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="memory_usage_graph">
<line-chart :chart-data="memoryUsageHistory.chartData" :options="{scales: {yAxes: [{ ticks: { max: 100, min: 0 }, scaleLabel: { display: true, labelString: 'Memory usage (%)' }}]}}"></line-chart>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<line-chart :chart-data="bandwithUsageHistory.chartData" :options="{title: {display: true, text: 'Network Usage'}, scales: {yAxes: [{ scaleLabel: { display: true, labelString: 'Mbit/sec' }}]}}"></line-chart>
<table class="table table-striped table-hover">
<tbody>
<tr>
<td>Incoming</td>
<td>{{ sysinfo.bandwith[0] | readableBits }}/sec</td>
</tr>
<tr>
<td>Outgoing</td>
<td>{{ sysinfo.bandwith[1] | readableBits }}/sec</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th v-for="(c, column) in columns">
<div v-on:click="sortBy(column)"
v-bind:class="{ 'text-info': sortKey == columns[column] && reverse,
'text-warning': sortKey == columns[column] && !reverse,
'sort': true }">{{ column }}</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="process in sorter">
<td>{{ process.pid }}</td>
<td>{{ process.name | truncate }}</td>
<td>{{ process.uid }}</td>
<td>{{ process.gid }}</td>
<td>{{ process.cpu_usage | round }}%</td>
<td>{{ process.memory | readableKbytes }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var sysinfo = new Vue({
el: '#sysinfo',
components: {
"line-chart": {
extends: VueChartJs.Line,
mixins: [VueChartJs.mixins.reactiveProp],
props: ['options'],
mounted: function() {
var options = {
animation: false,
responsive: true,
maintainAspectRatio: true,
};
Object.assign(options, this.options);
this.renderChart(this.chartData, options);
}
},
"doughnut-chart": {
extends: VueChartJs.Doughnut,
mixins: [VueChartJs.mixins.reactiveProp],
props: ['options'],
mounted: function() {
var options = {
animation: false,
responsive: true,
maintainAspectRatio: true,
};
Object.assign(options, this.options);
this.renderChart(this.chartData, options);
}
},
},
data: {
columns: {"PID": "pid", "Name": "name", "UID": "uid", "GID": "gid", "CPU": "cpu_usage", "Memory": "memory"},
sortKey: "cpu_usage",
processorUsageHistory: {
chartData: null,
history: [],
},
memoryUsageHistory: {
chartData: null,
history: [[], []],
},
bandwithUsageHistory: {
chartData: null,
history: [[], []],
},
temperatureHistory: {
chartData: null,
history: [],
},
memoryUsageChartData: null,
swapUsageChartData: null,
reverse: true,
sysinfo: null,
update_interval: 5000, chart_length: 300, chart_colors: [
'#69D2E7', '#FE4365', '#ECD078', '#53777A', '#FC9D9A', '#D95B43',
'#E0E4CC', '#F9CDAD', '#C02942', '#F38630', '#C8C8A9', '#542437',
'#FA6900', '#83AF9B', '#A7DBD8',
],
},
created: function() {
this.fetchData();
setInterval(function() {
this.fetchData();
}.bind(this), this.update_interval);
},
methods: {
fetchData: function() {
var xhr = new XMLHttpRequest();
var self = this;
xhr.open('GET', 'sysinfo.json');
xhr.onload = function() {
self.sysinfo = JSON.parse(xhr.responseText);
var values = [];
for (proc in self.sysinfo.process_list) {
values.push(self.sysinfo.process_list[proc]);
}
self.sysinfo.process_list = values;
self.addProcessorUsageHistory(self.sysinfo.processor_list.slice(1));
self.updateMemoryUsageChartData(self.sysinfo.memory);
self.updateBandwithUsageChartData(self.sysinfo.bandwith);
self.updateTemperatureHistory(self.sysinfo.components_list);
}
xhr.send();
},
addProcessorUsageHistory: function(data) {
var self = this;
var processor_list = [];
for (processor in data) {
processor_list.push({
name: data[processor].name,
usage: Math.floor(data[processor].cpu_usage * 100),
time: new Date(),
});
}
self.processorUsageHistory.history.push(processor_list);
if (self.processorUsageHistory.history.length > self.chart_length / (self.update_interval / 1000)) {
self.processorUsageHistory.history.shift();
}
self.updateProcessorUsageHistoryChartData();
},
updateProcessorUsageHistoryChartData: function() {
var self = this;
var datasets = [];
for (processor_index in self.processorUsageHistory.history[0]) {
var history = [];
for (i in self.processorUsageHistory.history) {
history.push(self.processorUsageHistory.history[i][processor_index].usage);
}
datasets.push({
label: self.processorUsageHistory.history[0][processor_index].name,
data: history,
borderColor: self.chart_colors[processor_index % self.chart_colors.length],
backgroundColor: 'rgba(255, 255, 255, 0)'
});
}
self.processorUsageHistory.chartData = {
labels: self.generateLabels(),
datasets: datasets,
};
},
generateLabels: function() {
var self = this;
var labels = [];
for (var i = 0; i <= self.chart_length; i = i + 5) {
var future = new Date(self.processorUsageHistory.history[0][0].time);
future.setSeconds(future.getSeconds() + i);
if (i % 60 == 0) {
labels.push(('0' + future.getHours()).slice(-2) + ':' +
('0' + future.getMinutes()).slice(-2) + ':' +
('0' + future.getSeconds()).slice(-2));
} else {
labels.push('');
}
}
return labels;
},
updateMemoryUsageChartData: function(memory) {
var self = this;
self.memoryUsageChartData = {
labels: [
"Used",
"Free"
],
datasets: [
{
data: [ memory[1], memory[2] ],
backgroundColor: [
self.chart_colors[1],
self.chart_colors[0],
],
}
]
};
self.swapUsageChartData = {
labels: [
"Used",
"Free"
],
datasets: [
{
data: [ memory[4], memory[5] ],
backgroundColor: [
self.chart_colors[1],
self.chart_colors[0],
],
}
]
};
self.memoryUsageHistory.history[0].push((memory[1] / memory[0] * 100).toFixed(2));
self.memoryUsageHistory.history[1].push((memory[4] / memory[3] * 100).toFixed(2));
if (self.memoryUsageHistory.history[0].length > self.chart_length / (self.update_interval / 1000)) {
self.memoryUsageHistory.history[0].shift();
self.memoryUsageHistory.history[1].shift();
}
self.memoryUsageHistory.chartData = {
labels: self.generateLabels(),
datasets: [
{
label: "Memory",
data: self.memoryUsageHistory.history[0],
borderColor: self.chart_colors[0],
backgroundColor: 'rgba(255, 255, 255, 0)'
},
{
label: "Swap",
data: self.memoryUsageHistory.history[1],
borderColor: self.chart_colors[1],
backgroundColor: 'rgba(255, 255, 255, 0)'
},
]
};
},
updateBandwithUsageChartData: function(bandwith) {
var self = this;
self.bandwithUsageHistory.history[0].push((bandwith[0] * 8 / 1024 / 1024).toFixed(2));
self.bandwithUsageHistory.history[1].push((bandwith[1] * 8 / 1024 / 1024).toFixed(2));
if (self.bandwithUsageHistory.history[0].length > self.chart_length / (self.update_interval / 1000)) {
self.bandwithUsageHistory.history[0].shift();
self.bandwithUsageHistory.history[1].shift();
}
self.bandwithUsageHistory.chartData = {
labels: self.generateLabels(),
datasets: [
{
label: "Incoming",
data: self.bandwithUsageHistory.history[0],
borderColor: self.chart_colors[0],
backgroundColor: 'rgba(255, 255, 255, 0)'
},
{
label: "Outgoing",
data: self.bandwithUsageHistory.history[1],
borderColor: self.chart_colors[1],
backgroundColor: 'rgba(255, 255, 255, 0)'
},
]
};
},
updateTemperatureHistory: function(components) {
var self = this;
var pos = 0;
if (self.temperatureHistory.history.length === 0) {
for (var i = 0; i < components.length; ++i) {
self.temperatureHistory.history.push({
label: components[i].label,
data: [],
borderColor: self.chart_colors[i % self.chart_colors.length],
backgroundColor: 'rgba(255, 255, 255, 0)',
});
}
}
for (var i = 0; i < components.length; ++i) {
self.temperatureHistory.history[i].data.push(components[i].temperature);
}
if (self.temperatureHistory.history.length > 0 &&
self.temperatureHistory.history[0].data.length > self.chart_length / (self.update_interval / 1000)) {
for (i = 0; i < self.temperatureHistory.history.length; ++i) {
self.temperatureHistory.history[i].data.shift();
}
}
self.temperatureHistory.chartData = {
labels: self.generateLabels(),
datasets: self.temperatureHistory.history,
};
},
sortBy: function(sortKey) {
this.reverse = (this.sortKey == this.columns[sortKey]) ? ! this.reverse : true;
this.sortKey = this.columns[sortKey];
},
},
computed: {
sorter: function() {
if (this.sysinfo == null) {
return [];
}
var self = this;
var order = self.reverse ? -1 : 1;
return this.sysinfo.process_list.sort(function(a, b) {
a = a[self.sortKey];
b = b[self.sortKey];
return (a === b ? 0 : a > b ? 1 : -1) * order;
});
},
},
filters: {
readableKbytes: function(kbytes) {
var thresh = 1024;
if(Math.abs(kbytes) < thresh) {
return kbytes + ' kiB';
}
var units = ['MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
kbytes /= thresh;
++u;
} while(Math.abs(kbytes) >= thresh && u < units.length - 1);
return kbytes.toFixed(1) + ' ' + units[u];
},
readableBytes: function(bytes) {
var thresh = 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = ['KiB', 'MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + ' ' + units[u];
},
readableBits: function(bytes) {
var thresh = 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = ['KB', 'MB','GB','TB','PB','EB','ZB','YB'];
var u = -1;
bytes *= 8;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + ' ' + units[u];
},
truncate: function(val) {
return val.substring(0, 100);
},
round: function(val) {
return Math.round(val)
},
},
});
</script>
</body>
</html>