statistics and navigation on landing page
This commit is contained in:
parent
5eed3bed89
commit
162624672c
@ -1,7 +1,25 @@
|
|||||||
import configparser
|
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):
|
class IniParser(configparser.ConfigParser):
|
||||||
|
"""
|
||||||
|
config output as json
|
||||||
|
"""
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
d = dict(self._sections)
|
d = dict(self._sections)
|
||||||
@ -9,3 +27,102 @@ class IniParser(configparser.ConfigParser):
|
|||||||
d[k] = dict(self._defaults, **d[k])
|
d[k] = dict(self._defaults, **d[k])
|
||||||
d[k].pop('__name__', None)
|
d[k].pop('__name__', None)
|
||||||
return d
|
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())
|
||||||
|
@ -4,7 +4,7 @@ from django.conf import settings
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from .utils import IniParser
|
from .utils import IniParser, SystemStats
|
||||||
|
|
||||||
|
|
||||||
class Config(APIView):
|
class Config(APIView):
|
||||||
@ -26,3 +26,17 @@ class Config(APIView):
|
|||||||
"error": "ffpayout engine config file not found!"})
|
"error": "ffpayout engine config file not found!"})
|
||||||
else:
|
else:
|
||||||
return Response({"success": False})
|
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})
|
||||||
|
@ -154,3 +154,6 @@ STATIC_URL = '/static/'
|
|||||||
|
|
||||||
# path to ffplayout engine config
|
# path to ffplayout engine config
|
||||||
FFPLAYOUT_CONFIG = '/etc/ffplayout/ffplayout.conf'
|
FFPLAYOUT_CONFIG = '/etc/ffplayout/ffplayout.conf'
|
||||||
|
|
||||||
|
# used network interface for statistics: eth0, eno1, etc.
|
||||||
|
NET_INTERFACE = 'br0'
|
||||||
|
@ -30,6 +30,7 @@ urlpatterns = [
|
|||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/', include(router.urls)),
|
path('api/', include(router.urls)),
|
||||||
path('api/config/', views.Config.as_view()),
|
path('api/config/', views.Config.as_view()),
|
||||||
|
path('api/stats/', views.Statistics.as_view()),
|
||||||
path('api-auth/', include(
|
path('api-auth/', include(
|
||||||
'rest_framework.urls', namespace='rest_framework')),
|
'rest_framework.urls', namespace='rest_framework')),
|
||||||
path('auth/token/', TokenObtainPairView.as_view(),
|
path('auth/token/', TokenObtainPairView.as_view(),
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
html, body {
|
html, body {
|
||||||
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||||
Roboto, 'Helvetica Neue', Arial, sans-serif;
|
Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
word-spacing: 1px;
|
word-spacing: 1px;
|
||||||
-ms-text-size-adjust: 100%;
|
-ms-text-size-adjust: 100%;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!$store.state.auth.isLogin">
|
||||||
|
<div class="logout-div" />
|
||||||
<b-container class="login-container">
|
<b-container class="login-container">
|
||||||
<div>
|
<div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>ffplayout</h1>
|
<h1>ffplayout</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!$store.state.auth.isLogin">
|
|
||||||
<b-form @submit.prevent="login" class="login-form">
|
<b-form @submit.prevent="login" class="login-form">
|
||||||
<p v-if="formError" class="error">
|
<p v-if="formError" class="error">
|
||||||
{{ formError }}
|
{{ formError }}
|
||||||
@ -21,13 +22,127 @@
|
|||||||
</b-button>
|
</b-button>
|
||||||
</b-form>
|
</b-form>
|
||||||
</div>
|
</div>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
<b-container class="login-container">
|
||||||
|
<div>
|
||||||
|
<b-row cols="3">
|
||||||
|
<b-col cols="4" class="chart-col chart1">
|
||||||
<br>
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</b-container>
|
</b-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -38,12 +153,19 @@ export default {
|
|||||||
return {
|
return {
|
||||||
formError: null,
|
formError: null,
|
||||||
formUsername: '',
|
formUsername: '',
|
||||||
formPassword: ''
|
formPassword: '',
|
||||||
|
interval: null,
|
||||||
|
stat: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init () {
|
async init () {
|
||||||
await this.$store.dispatch('auth/inspectToken')
|
await this.$store.dispatch('auth/inspectToken')
|
||||||
@ -75,7 +197,16 @@ export default {
|
|||||||
checkLogin () {
|
checkLogin () {
|
||||||
if (this.$store.state.auth.isLogin) {
|
if (this.$store.state.auth.isLogin) {
|
||||||
// this.$router.push('/player')
|
// 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 {
|
.login-form {
|
||||||
min-width: 300px;
|
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>
|
</style>
|
||||||
|
@ -37,7 +37,6 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
await this.$axios.post('auth/token/', payload)
|
await this.$axios.post('auth/token/', payload)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log('obtainToken: ', response)
|
|
||||||
commit('UPADTE_TOKEN', { token: response.data.access, refresh: response.data.refresh })
|
commit('UPADTE_TOKEN', { token: response.data.access, refresh: response.data.refresh })
|
||||||
commit('UPDATE_IS_LOGIN', true)
|
commit('UPDATE_IS_LOGIN', true)
|
||||||
})
|
})
|
||||||
@ -47,11 +46,11 @@ export const actions = {
|
|||||||
},
|
},
|
||||||
async refreshToken ({ commit, state }) {
|
async refreshToken ({ commit, state }) {
|
||||||
const payload = {
|
const payload = {
|
||||||
refresh: state.jwtRefresh
|
refresh: state.jwtRefresh,
|
||||||
|
progress: false
|
||||||
}
|
}
|
||||||
const response = await this.$axios.post('auth/token/refresh/', payload)
|
const response = await this.$axios.post('auth/token/refresh/', payload)
|
||||||
|
|
||||||
console.log('refreshToken: ', response)
|
|
||||||
commit('UPADTE_TOKEN', { token: response.data.access })
|
commit('UPADTE_TOKEN', { token: response.data.access })
|
||||||
commit('UPDATE_IS_LOGIN', true)
|
commit('UPDATE_IS_LOGIN', true)
|
||||||
},
|
},
|
||||||
@ -68,18 +67,14 @@ export const actions = {
|
|||||||
if (expire_token - timestamp > 0) {
|
if (expire_token - timestamp > 0) {
|
||||||
// DO NOTHING, DO NOT REFRESH
|
// DO NOTHING, DO NOT REFRESH
|
||||||
commit('UPDATE_IS_LOGIN', true)
|
commit('UPDATE_IS_LOGIN', true)
|
||||||
console.log('token is valid, for: ' + Math.floor(expire_token - timestamp) + ' seconds')
|
|
||||||
} else if (expire_refresh - timestamp > 0) {
|
} else if (expire_refresh - timestamp > 0) {
|
||||||
await dispatch('refreshToken')
|
await dispatch('refreshToken')
|
||||||
console.log('update token')
|
|
||||||
} else {
|
} else {
|
||||||
// PROMPT USER TO RE-LOGIN, THIS ELSE CLAUSE COVERS THE CONDITION WHERE A TOKEN IS EXPIRED AS WELL
|
// PROMPT USER TO RE-LOGIN, THIS ELSE CLAUSE COVERS THE CONDITION WHERE A TOKEN IS EXPIRED AS WELL
|
||||||
commit('UPDATE_IS_LOGIN', false)
|
commit('UPDATE_IS_LOGIN', false)
|
||||||
console.log('new login')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
commit('UPDATE_IS_LOGIN', false)
|
commit('UPDATE_IS_LOGIN', false)
|
||||||
console.log('new login')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user