statistics and navigation on landing page

This commit is contained in:
Jonathan Baecker 2020-01-30 17:17:26 +01:00
parent 5eed3bed89
commit 162624672c
7 changed files with 357 additions and 38 deletions

View File

@ -1,7 +1,25 @@
import configparser
from platform import uname
from time import sleep
from django.conf import settings
import psutil
def sizeof_fmt(num, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
class IniParser(configparser.ConfigParser):
"""
config output as json
"""
def as_dict(self):
d = dict(self._sections)
@ -9,3 +27,102 @@ class IniParser(configparser.ConfigParser):
d[k] = dict(self._defaults, **d[k])
d[k].pop('__name__', None)
return d
class SystemStats:
def __init__(self):
pass
def all(self):
return {
**self.system(),
**self.cpu(), **self.ram(), **self.swap(),
**self.disk(), **self.net(), **self.net_speed()
}
def system(self):
return {
'system': uname().system,
'node': uname().node,
'machine': uname().machine
}
def cpu(self):
return {
'cpu_usage': psutil.cpu_percent(interval=1),
'cpu_load': list(psutil.getloadavg())
}
def ram(self):
mem = psutil.virtual_memory()
return {
'ram_total': [mem.total, sizeof_fmt(mem.total)],
'ram_used': [mem.used, sizeof_fmt(mem.used)],
'ram_free': [mem.free, sizeof_fmt(mem.free)],
'ram_cached': [mem.cached, sizeof_fmt(mem.cached)]
}
def swap(self):
swap = psutil.swap_memory()
return {
'swap_total': [swap.total, sizeof_fmt(swap.total)],
'swap_used': [swap.used, sizeof_fmt(swap.used)],
'swap_free': [swap.free, sizeof_fmt(swap.free)]
}
def disk(self):
root = psutil.disk_usage('/')
return {
'disk_total': [root.total, sizeof_fmt(root.total)],
'disk_used': [root.used, sizeof_fmt(root.used)],
'disk_free': [root.free, sizeof_fmt(root.free)]
}
def net(self):
net = psutil.net_io_counters()
return {
'net_send': [net.bytes_sent, sizeof_fmt(net.bytes_sent)],
'net_recv': [net.bytes_recv, sizeof_fmt(net.bytes_recv)],
'net_errin': net.errin,
'net_errout': net.errout
}
def net_speed(self):
net = psutil.net_if_stats()
if settings.NET_INTERFACE not in net:
return {
'net_speed_send': 'no network interface set!',
'net_speed_recv': 'no network interface set!'
}
net = psutil.net_io_counters(pernic=True)[settings.NET_INTERFACE]
send_start = net.bytes_sent
recv_start = net.bytes_recv
sleep(1)
net = psutil.net_io_counters(pernic=True)[settings.NET_INTERFACE]
send_end = net.bytes_sent
recv_end = net.bytes_recv
send_sec = send_end - send_start
recv_sec = recv_end - recv_start
return {
'net_speed_send': [send_sec, sizeof_fmt(send_sec)],
'net_speed_recv': [recv_sec, sizeof_fmt(recv_sec)]
}
if __name__ == '__main__':
print('ALL: ', SystemStats().all())
exit()
print('CPU: ', SystemStats.cpu())
print('RAM: ', SystemStats.ram())
print('SWAP: ', SystemStats.swap())
print('DISK: ', SystemStats.disk())
print('NET: ', SystemStats.net())
print('SPEED: ', SystemStats.net_speed())

View File

@ -4,7 +4,7 @@ from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from .utils import IniParser
from .utils import IniParser, SystemStats
class Config(APIView):
@ -26,3 +26,17 @@ class Config(APIView):
"error": "ffpayout engine config file not found!"})
else:
return Response({"success": False})
class Statistics(APIView):
"""
get system statistics: cpu, ram, etc.
for reading, endpoint is: http://127.0.0.1:8000/api/stats/?stats=all
"""
def get(self, request, *args, **kwargs):
if 'stats' in request.GET.dict() \
and request.GET.dict()['stats'] == 'all':
return Response(SystemStats().all())
else:
return Response({"success": False})

View File

@ -154,3 +154,6 @@ STATIC_URL = '/static/'
# path to ffplayout engine config
FFPLAYOUT_CONFIG = '/etc/ffplayout/ffplayout.conf'
# used network interface for statistics: eth0, eno1, etc.
NET_INTERFACE = 'br0'

View File

@ -30,6 +30,7 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('api/config/', views.Config.as_view()),
path('api/stats/', views.Statistics.as_view()),
path('api-auth/', include(
'rest_framework.urls', namespace='rest_framework')),
path('auth/token/', TokenObtainPairView.as_view(),

View File

@ -8,7 +8,7 @@
html, body {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
font-size: 15px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;

View File

@ -1,11 +1,12 @@
<template>
<div>
<div v-if="!$store.state.auth.isLogin">
<div class="logout-div" />
<b-container class="login-container">
<div>
<div class="header">
<h1>ffplayout</h1>
</div>
<div v-if="!$store.state.auth.isLogin">
<b-form @submit.prevent="login" class="login-form">
<p v-if="formError" class="error">
{{ formError }}
@ -21,13 +22,127 @@
</b-button>
</b-form>
</div>
</b-container>
</div>
<div v-else>
<b-container class="login-container">
<div>
<b-row cols="3">
<b-col cols="4" class="chart-col chart1">
<br>
<div class="stat-div">
<div class="stat-center" style="text-align: left;">
<h1>ffplayout</h1>
<h3 v-if="stat.system">
{{ stat.system }}<br>
{{ stat.node }}<br>
{{ stat.machine }}
</h3>
</div>
</div>
</b-col>
<b-col cols="4" class="chart-col chart2">
<div v-if="stat.cpu_usage">
<div>
<strong>CPU</strong>
</div>
<div class="stat-div">
<div class="stat-center">
<b-progress :value="stat.cpu_usage" max="100" variant="success" height="1rem" />
<br>
<h3>Wellcome to ffplayout manager!</h3>
<div style="text-align: left;">
<strong>Usage: </strong>{{ stat.cpu_usage }}%<br>
<strong>Load: </strong> {{ stat.cpu_load[0] }} {{ stat.cpu_load[1] }} {{ stat.cpu_load[2] }}
</div>
</div>
</div>
</div>
</b-col>
<b-col cols="4" class="chart-col chart3">
<div v-if="stat.ram_total">
<div>
<strong>RAM</strong>
</div>
<div class="stat-div">
<div class="stat-center">
<div style="text-align: left;">
<strong>Total: </strong> {{ stat.ram_total[1] }}<br>
<strong>Used: </strong> {{ stat.ram_used[1] }}<br>
<strong>Free: </strong> {{ stat.ram_free[1] }}<br>
<strong>Cached: </strong> {{ stat.ram_cached[1] }}
</div>
</div>
</div>
</div>
</b-col>
<b-col cols="4" class="chart-col chart4">
<div v-if="stat.swap_total">
<div>
<strong>SWAP</strong>
</div>
<div class="stat-div">
<div class="stat-center">
<div style="text-align: left;">
<strong>Total: </strong> {{ stat.swap_total[1] }}<br>
<strong>Used: </strong> {{ stat.swap_used[1] }}<br>
<strong>Free: </strong> {{ stat.swap_free[1] }}
</div>
</div>
</div>
</div>
</b-col>
<b-col cols="4" class="chart-col chart5">
<div v-if="stat.disk_total">
<div>
<strong>DISK</strong>
</div>
<div class="stat-div">
<div class="stat-center">
<div style="text-align: left;">
<strong>Total: </strong> {{ stat.disk_total[1] }}<br>
<strong>Used: </strong> {{ stat.disk_used[1] }}<br>
<strong>Free: </strong> {{ stat.disk_free[1] }}
</div>
</div>
</div>
</div>
</b-col>
<b-col cols="4" class="chart-col chart6">
<div v-if="stat.net_send">
<div>
<strong>NET</strong>
</div>
<div class="stat-div">
<div class="stat-center">
<div style="text-align: left;">
<strong>Download: </strong> {{ stat.net_speed_recv[1] }}/s<br>
<strong>Upload: </strong> {{ stat.net_speed_send[1] }}/s<br>
<strong>Downloaded: </strong> {{ stat.net_recv[1] }}<br>
<strong>Uploaded: </strong> {{ stat.net_send[1] }}<br>
<strong>Recived Errors: </strong> {{ stat.net_errin }}<br>
<strong>Sended Errors: </strong> {{ stat.net_errout }}
</div>
</div>
</div>
</div>
</b-col>
</b-row>
<div class="actions">
<b-button-group class="actions-grp">
<b-button>Control</b-button>
<b-button>Media</b-button>
<b-button>Logging</b-button>
<b-button>Configure</b-button>
<b-button @click="logout()">
Logout
</b-button>
</b-button-group>
</div>
</div>
</b-container>
</div>
</div>
</template>
<script>
@ -38,12 +153,19 @@ export default {
return {
formError: null,
formUsername: '',
formPassword: ''
formPassword: '',
interval: null,
stat: {}
}
},
created () {
this.init()
},
beforeDestroy () {
if (this.interval) {
clearInterval(this.interval)
}
},
methods: {
async init () {
await this.$store.dispatch('auth/inspectToken')
@ -75,7 +197,16 @@ export default {
checkLogin () {
if (this.$store.state.auth.isLogin) {
// this.$router.push('/player')
this.sysStats()
}
},
sysStats () {
this.interval = setInterval(async () => {
await this.$store.dispatch('auth/inspectToken')
const response = await this.$axios.get('api/stats/?stats=all', { headers: { Authorization: 'Bearer ' + this.$store.state.auth.jwtToken }, progress: false })
this.stat = response.data
}, 2000)
}
}
}
@ -97,4 +228,62 @@ export default {
.login-form {
min-width: 300px;
}
.manage-btn {
margin: 0 auto 0 auto;
}
.chart-col {
text-align: center;
min-width: 10em;
min-height: 15em;
border: solid #c3c3c3;
}
.stat-div {
padding-top: .5em;
position: relative;
height: 12em;
}
.stat-center {
margin: 0;
position: absolute;
width: 100%;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
.chart1 {
background: rgba(210, 85, 23, 0.1);
}
.chart2 {
background: rgba(122, 210, 23, 0.1);
}
.chart3 {
background: rgba(23, 210, 149, 0.1);
}
.chart4 {
background: rgba(23, 160, 210, 0.1);
}
.chart5 {
background: rgba(122, 23, 210, 0.1);
}
.chart6 {
background: rgba(210, 23, 74, 0.1);
}
.actions {
text-align: center;
margin-top: 1em;
}
@media (max-width: 380px) {
.actions-grp {
display: flex;
flex-direction: column;
margin: 0 2em 0 2em;
}
}
</style>

View File

@ -37,7 +37,6 @@ export const actions = {
}
await this.$axios.post('auth/token/', payload)
.then((response) => {
console.log('obtainToken: ', response)
commit('UPADTE_TOKEN', { token: response.data.access, refresh: response.data.refresh })
commit('UPDATE_IS_LOGIN', true)
})
@ -47,11 +46,11 @@ export const actions = {
},
async refreshToken ({ commit, state }) {
const payload = {
refresh: state.jwtRefresh
refresh: state.jwtRefresh,
progress: false
}
const response = await this.$axios.post('auth/token/refresh/', payload)
console.log('refreshToken: ', response)
commit('UPADTE_TOKEN', { token: response.data.access })
commit('UPDATE_IS_LOGIN', true)
},
@ -68,18 +67,14 @@ export const actions = {
if (expire_token - timestamp > 0) {
// DO NOTHING, DO NOT REFRESH
commit('UPDATE_IS_LOGIN', true)
console.log('token is valid, for: ' + Math.floor(expire_token - timestamp) + ' seconds')
} else if (expire_refresh - timestamp > 0) {
await dispatch('refreshToken')
console.log('update token')
} else {
// PROMPT USER TO RE-LOGIN, THIS ELSE CLAUSE COVERS THE CONDITION WHERE A TOKEN IS EXPIRED AS WELL
commit('UPDATE_IS_LOGIN', false)
console.log('new login')
}
} else {
commit('UPDATE_IS_LOGIN', false)
console.log('new login')
}
}
}