add dashboard, fix: ffplayout:#415
This commit is contained in:
parent
fead22adea
commit
4e742861df
@ -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)
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ $log-server: #23cbdd;
|
|||||||
|
|
||||||
$theme-colors: (
|
$theme-colors: (
|
||||||
'primary': $primary,
|
'primary': $primary,
|
||||||
|
'secondary': $secondary,
|
||||||
);
|
);
|
||||||
|
|
||||||
$b-radius: 3px;
|
$b-radius: 3px;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
BIN
docs/images/dasboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user