add dashboard, fix: ffplayout:#415

This commit is contained in:
jb-alvarado 2023-11-16 15:45:46 +01:00
parent fead22adea
commit 4e742861df
7 changed files with 146 additions and 77 deletions

View File

@ -12,6 +12,9 @@ Read the [install.md](docs/INSTALL.md) for manual installation.
### Login ### Login
![login](/docs/images/login.png) ![login](/docs/images/login.png)
### Login
![login](/docs/images/dasboard.png)
### Landing Page ### Landing Page
![landing](/docs/images/landing.png) ![landing](/docs/images/landing.png)

View File

@ -42,6 +42,7 @@ $log-server: #23cbdd;
$theme-colors: ( $theme-colors: (
'primary': $primary, 'primary': $primary,
'secondary': $secondary,
); );
$b-radius: 3px; $b-radius: 3px;

View File

@ -24,6 +24,9 @@
</button> </button>
<div class="collapse navbar-collapse justify-content-end" id="navbarNavDropdown"> <div class="collapse navbar-collapse justify-content-end" id="navbarNavDropdown">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/">Home</NuxtLink>
</li>
<li class="nav-item"> <li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/player">Player</NuxtLink> <NuxtLink class="btn btn-primary btn-sm" to="/player">Player</NuxtLink>
</li> </li>

View File

@ -1,14 +1,60 @@
<template> <template>
<div class="row sys-container text-start"> <div class="row sys-container text-start bg-secondary border border-3 rounded">
<div class="col-4 h-100 bg-warning"> <div class="col-6 bg-primary p-2 border-top border-start border-end fs-1">
{{ sysStat.system.name }} {{ sysStat.system.version }} {{ sysStat.system.kernel }} {{ sysStat.system.name }} {{ sysStat.system.version }}
</div>
<div class="col-6 p-2 border">
<div class="fs-4">CPU</div>
<div class="row">
<div class="col"><strong>Cores:</strong> {{ sysStat.cpu.cores }}</div>
<div class="col"><strong>Usage:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div>
</div>
</div>
<div v-if="sysStat.system.kernel" class="col-6 bg-primary border-start border-end border-bottom">
{{ sysStat.system.kernel }}
</div>
<div class="col-6 p-2 border">
<div class="fs-4">Load</div>
<div class="row">
<div class="col">{{ sysStat.load.one }}</div>
<div class="col">{{ sysStat.load.five }}</div>
<div class="col">{{ sysStat.load.fifteen }}</div>
</div>
</div>
<div class="col-6 border">
<div class="fs-4">Memory</div>
<div class="row">
<div class="col"><strong>Total:</strong> {{ fileSize(sysStat.memory.total) }}</div>
<div class="col"><strong>Usage:</strong> {{ fileSize(sysStat.memory.used) }}</div>
</div>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">Swap</div>
<div class="row">
<div class="col"><strong>Total:</strong> {{ fileSize(sysStat.swap.total) }}</div>
<div class="col"><strong>Usage:</strong> {{ fileSize(sysStat.swap.used) }}</div>
</div>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">
Network <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span>
</div>
<div class="row">
<div class="col-6"><strong>In:</strong> {{ fileSize(sysStat.network?.current_in) }}</div>
<div class="col-6"><strong>Out:</strong> {{ fileSize(sysStat.network?.current_out) }}</div>
<div class="col-6">{{ fileSize(sysStat.network?.total_in) }}</div>
<div class="col-6">{{ fileSize(sysStat.network?.total_out) }}</div>
</div>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">Storage</div>
<div v-if="sysStat.storage"><strong>Device:</strong> {{ sysStat.storage?.path }}</div>
<div class="row" v-if="sysStat.storage">
<div class="col"><strong>Size:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
<div class="col"><strong>Used:</strong> {{ fileSize(sysStat.storage?.used) }}</div>
</div>
</div> </div>
<div class="col-5">CPU Cores: {{ sysStat.cpu.cores }} Used {{ sysStat.cpu.usage }}</div>
<div class="col-5">Load: {{ sysStat.load.one }} | {{ sysStat.load.five }} | {{ sysStat.load.fifteen }}</div>
<div class="col-5">Memory Total: {{ fileSize(sysStat.memory.total) }} Used: {{ fileSize(sysStat.memory.used) }}</div>
<div class="col-5">Network Name: {{ sysStat.network?.name }} In: {{ fileSize(sysStat.network?.current_in) }}</div>
<div class="col-5">Storage Path: {{ sysStat.storage?.path }} Size: {{ fileSize(sysStat.storage?.total) }} Used: {{fileSize(sysStat.storage?.used)}}</div>
<div class="col-5">Swap Total: {{ fileSize(sysStat.swap.total) }} Used: {{ fileSize(sysStat.swap.used) }}</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -22,14 +68,14 @@ const configStore = useConfig()
const contentType = { 'content-type': 'application/json,charset=UTF-8' } const contentType = { 'content-type': 'application/json,charset=UTF-8' }
const timer = ref() const timer = ref()
const sysStat = ref({ const sysStat = ref({
cpu: { cores: 0.0, usage: 0.0 }, cpu: { cores: 0.0, usage: 0.0 },
load: { one: 0.0, five: 0.0, fifteen: 0.0 }, load: { one: 0.0, five: 0.0, fifteen: 0.0 },
memory: { total: 0.0, used: 0.0, free: 0.0 }, memory: { total: 0.0, used: 0.0, free: 0.0 },
network: { name: "", current_in: 0.0, current_out: 0.0, total_in: 0.0, total_out: 0.0 }, network: { name: '', current_in: 0.0, current_out: 0.0, total_in: 0.0, total_out: 0.0 },
storage: { path: "", total: 0.0, used: 0.0 }, storage: { path: '', total: 0.0, used: 0.0 },
swap: { total: 0.0, used: 0.0, free: 0.0 }, swap: { total: 0.0, used: 0.0, free: 0.0 },
system: { name: "", kernel: "", version: "" }, system: { name: '', kernel: '', version: '' },
} as SystemStatistics) } as SystemStatistics)
onMounted(() => { onMounted(() => {
status() status()
@ -58,17 +104,17 @@ async function status() {
async function systemStatus() { async function systemStatus() {
const channel = configStore.configGui[configStore.configID].id const channel = configStore.configGui[configStore.configID].id
await $fetch(`/api/system/${channel}`, { await $fetch<SystemStatistics>(`/api/system/${channel}`, {
method: 'GET', method: 'GET',
headers: { ...contentType, ...authStore.authHeader }, headers: { ...contentType, ...authStore.authHeader },
}).then((stat: SystemStatistics) => { }).then((stat: SystemStatistics) => {
console.log(stat)
sysStat.value = stat sysStat.value = stat
}) })
} }
</script> </script>
<style> <style>
.sys-container { .sys-container {
min-height: 500px, max-width: 640px;
min-height: 300px;
} }
</style> </style>

View File

@ -1,47 +1,42 @@
export const stringFormatter = () => { export const stringFormatter = () => {
function fileSize(bytes: number | undefined, si=false, dp=1) { function fileSize(bytes: number | undefined, dp = 2) {
if (!bytes) { if (!bytes) {
return 0.0 return 0.0
} }
const thresh = si ? 1000 : 1024; const thresh = 1024
if (Math.abs(bytes) < thresh) { if (Math.abs(bytes) < thresh) {
return bytes + ' B'; return bytes + ' B'
} }
const units = si const units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] let u = -1
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; const r = 10 ** dp
let u = -1;
const r = 10**dp;
do { do {
bytes /= thresh; bytes /= thresh
++u; ++u
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
return bytes.toFixed(dp) + ' ' + units[u]
return bytes.toFixed(dp) + ' ' + units[u];
} }
function formatLog(text: string) { function formatLog(text: string) {
return ( return text
text .replace(/\x1B\[33m(.*?)\x1B\[0m/g, '<span class="log-number">$1</span>')
.replace(/\x1B\[33m(.*?)\x1B\[0m/g, '<span class="log-number">$1</span>') .replace(/\x1B\[1m\x1B\[35m(.*?)\x1B\[0m\x1B\[22m/g, '<span class="log-addr">$1</span>')
.replace(/\x1B\[1m\x1B\[35m(.*?)\x1B\[0m\x1B\[22m/g, '<span class="log-addr">$1</span>') .replace(/\x1B\[94m(.*?)\x1B\[0m/g, '<span class="log-cmd">$1</span>')
.replace(/\x1B\[94m(.*?)\x1B\[0m/g, '<span class="log-cmd">$1</span>') .replace(/\x1B\[90m(.*?)\x1B\[0m/g, '<span class="log-debug">$1</span>')
.replace(/\x1B\[90m(.*?)\x1B\[0m/g, '<span class="log-debug">$1</span>') .replace(/(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.[\d]+\])/g, '<span class="log-time">$1</span>')
.replace(/(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.[\d]+\])/g, '<span class="log-time">$1</span>') .replace(/\[ INFO\]/g, '<span class="log-info">[ INFO]</span>')
.replace(/\[ INFO\]/g, '<span class="log-info">[ INFO]</span>') .replace(/\[ WARN\]/g, '<span class="log-warning">[ WARN]</span>')
.replace(/\[ WARN\]/g, '<span class="log-warning">[ WARN]</span>') .replace(/\[ERROR\]/g, '<span class="log-error">[ERROR]</span>')
.replace(/\[ERROR\]/g, '<span class="log-error">[ERROR]</span>') .replace(/\[DEBUG\]/g, '<span class="log-debug">[DEBUG]</span>')
.replace(/\[DEBUG\]/g, '<span class="log-debug">[DEBUG]</span>') .replace(/\[Decoder\]/g, '<span class="log-decoder">[Decoder]</span>')
.replace(/\[Decoder\]/g, '<span class="log-decoder">[Decoder]</span>') .replace(/\[Encoder\]/g, '<span class="log-encoder">[Encoder]</span>')
.replace(/\[Encoder\]/g, '<span class="log-encoder">[Encoder]</span>') .replace(/\[Server\]/g, '<span class="log-server">[Server]</span>')
.replace(/\[Server\]/g, '<span class="log-server">[Server]</span>') .replace(/\[Validator\]/g, '<span class="log-server">[Validator]</span>')
.replace(/\[Validator\]/g, '<span class="log-server">[Validator]</span>')
)
} }
function timeToSeconds(time: string) { function timeToSeconds(time: string) {
@ -99,9 +94,40 @@ export const stringFormatter = () => {
} }
function mediaType(path: string) { function mediaType(path: string) {
const videoType = ['avi', 'flv', 'm2v', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mts', 'mxf', 'ts', 'vob','ogv', 'webm', 'wmv'] const videoType = [
'avi',
'flv',
'm2v',
'm4v',
'mkv',
'mov',
'mp4',
'mpeg',
'mpg',
'mts',
'mxf',
'ts',
'vob',
'ogv',
'webm',
'wmv',
]
const audioType = ['aac', 'aiff', 'flac', 'm4a', 'mp2', 'mp3', 'ogg', 'opus', 'wav', 'wma'] const audioType = ['aac', 'aiff', 'flac', 'm4a', 'mp2', 'mp3', 'ogg', 'opus', 'wav', 'wma']
const imageType = ['apng', 'avif', 'bmp', 'exr', 'gif', 'jpeg', 'jpg', 'png', 'psd', 'tga', 'tif', 'tiff', 'webp'] const imageType = [
'apng',
'avif',
'bmp',
'exr',
'gif',
'jpeg',
'jpg',
'png',
'psd',
'tga',
'tif',
'tiff',
'webp',
]
const ext = path.split('.').pop() const ext = path.split('.').pop()
if (ext) { if (ext) {
@ -117,15 +143,23 @@ export const stringFormatter = () => {
return null return null
} }
return { fileSize, formatLog, timeToSeconds, secToHMS, numberToHex, hexToNumber, filename, toMin, secondsToTime, mediaType } return {
fileSize,
formatLog,
timeToSeconds,
secToHMS,
numberToHex,
hexToNumber,
filename,
toMin,
secondsToTime,
mediaType,
}
} }
export const playlistOperations = () => { export const playlistOperations = () => {
function genUID() { function genUID() {
return String( return String(Date.now().toString(32) + Math.random().toString(16)).replace(/\./g, '')
Date.now().toString(32) +
Math.random().toString(16)
).replace(/\./g, '')
} }
function processPlaylist(dayStart: number, length: number, list: PlaylistItem[], forSave: boolean) { function processPlaylist(dayStart: number, length: number, list: PlaylistItem[], forSave: boolean) {

BIN
docs/images/dasboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -3,10 +3,8 @@
<div v-if="authStore.isLogin"> <div v-if="authStore.isLogin">
<div class="container login-container"> <div class="container login-container">
<div> <div>
<div class="logo-div"> <SystemStats class="mx-auto" />
<SystemStats /> <div class="text-center mt-5">
</div>
<div class="actions">
<div class="btn-group actions-grp btn-group-lg" role="group"> <div class="btn-group actions-grp btn-group-lg" role="group">
<NuxtLink to="/player" class="btn btn-primary">Player</NuxtLink> <NuxtLink to="/player" class="btn btn-primary">Player</NuxtLink>
<NuxtLink to="/media" class="btn btn-primary">Media</NuxtLink> <NuxtLink to="/media" class="btn btn-primary">Media</NuxtLink>
@ -23,7 +21,7 @@
<div class="logout-div" /> <div class="logout-div" />
<div class="container login-container"> <div class="container login-container">
<div> <div>
<div class="header"> <div class="text-center mb-5">
<h1>ffplayout</h1> <h1>ffplayout</h1>
</div> </div>
@ -127,17 +125,6 @@ async function logout() {
height: 100vh; height: 100vh;
} }
.header {
text-align: center;
margin-bottom: 3em;
}
.logo-div {
width: 100%;
text-align: center;
margin-bottom: 5em;
}
.login-form { .login-form {
min-width: 300px; min-width: 300px;
} }
@ -151,11 +138,6 @@ async function logout() {
} }
} }
.actions {
text-align: center;
margin-top: 1em;
}
@media (max-width: 380px) { @media (max-width: 380px) {
.actions-grp { .actions-grp {
display: flex; display: flex;