307 lines
14 KiB
PHP
307 lines
14 KiB
PHP
<?php include ("./link/session.php") ?>
|
|
<!doctype html>
|
|
<html lang="uft-8">
|
|
<head>
|
|
<?php include ("./public/head.inc") ?>
|
|
</head>
|
|
<body>
|
|
<?php include ("./public/menu.inc") ?>
|
|
<div data-simplebar>
|
|
<main class="page-content dashboard" id="app" v-cloak>
|
|
<div class="row">
|
|
<div class="col-lg-6">
|
|
<div class="card">
|
|
<div class="card-header bg-transparent">
|
|
<div class="p-2 mb-0 d-flex align-items-end">
|
|
<cn>系统状态</cn>
|
|
<en>System state</en>
|
|
</div>
|
|
</div>
|
|
<div class="card-body" >
|
|
<div class="row row-cols-3 text-center">
|
|
<div class="col-lg-4 ">
|
|
<pie-chart v-model="cpu" :active-color="theme_color"></pie-chart>
|
|
<div>
|
|
<cn>CPU使用率</cn>
|
|
<en>CPU usage</en>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4 text-center">
|
|
<pie-chart v-model="mem" :active-color="theme_color"></pie-chart>
|
|
<div>
|
|
<cn>内存使用率</cn>
|
|
<en>Memory usage</en>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4 text-center">
|
|
<tmp-compt v-model="tmp" :active-color="theme_color"></tmp-compt>
|
|
<div>
|
|
<cn>核心温度</cn>
|
|
<en>Core temperature</en>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-6">
|
|
<div class="card">
|
|
<div class="card-header bg-transparent">
|
|
<div class="p-2 mb-0 d-flex align-items-end">
|
|
<cn>网络状态</cn>
|
|
<en>Network state</en>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<net-chart v-if="tx.length > 0" :maxy="maxy" :data1="tx" :data2="rx" :key="netFlotKey"
|
|
:line1-color="theme_color" :line2-color="line2_color" :tick-color="tickColor" :border-color="borderColor"
|
|
:tip-border-color="tipBorderColor" :tip-bg-color="tipBgColor" :tip-txt-color="tipTxtColor"></net-chart>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
<div class="card">
|
|
<div class="card-body iface py-3">
|
|
<div v-for="(item,index) in input" :key="index" :class="[{'ms-5':index > 0},{'hdmi':item.protocol==='HDMI'},{'sdi':item.protocol==='SDI' || item.protocol==='AHD'},{'disable':!item.avalible}]">
|
|
<span class="info">{{item.info}}</span>
|
|
<div class="icon my-1"></div>
|
|
<span class="name">{{item.name}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-2">
|
|
<div class="col-lg-12">
|
|
<div class="card">
|
|
<div class="card-header bg-transparent">
|
|
<div class="p-2 mb-0 d-flex align-items-end">
|
|
<cn>预览</cn>
|
|
<en>Preview</en>
|
|
<small style="margin-left: 5px;color: grey;font-size: 12px;">
|
|
<cn>非实时视频,仅预览图片</cn>
|
|
<en>Not a realtime video, picture only</en>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row row-cols-2 row-cols-lg-4 g-3">
|
|
<div v-for="(item,index) in preview" :key="index" class="col">
|
|
<div class="card">
|
|
<img :src="makeImgUrl(item.id)" class="card-img-top">
|
|
<div class="chn-volume" :style="{'width':handleChnVolume(item.id,'L')}"></div>
|
|
<div class="chn-volume" :style="{'width':handleChnVolume(item.id,'R')}"></div>
|
|
<div class="card-body">
|
|
<p class="card-text text-center">{{item.name}}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<?php include ("./public/foot.inc") ?>
|
|
<script src="assets/plugins/easyPieChart/jquery.easypiechart.js" type="module"></script>
|
|
<script src="assets/plugins/flotChart/jquery.flot.js" type="module"></script>
|
|
<script src="assets/plugins/flotChart/jquery.flot.resize.js" type="module"></script>
|
|
|
|
<script type="module">
|
|
import { rpc } from "./assets/js/lp.utils.js";
|
|
import { useDefaultConf } from "./assets/js/vue.hooks.js";
|
|
import { ignoreCustomElementPlugin,filterKeywordPlugin,bootstrapSwitchComponent,statusPieChartComponent,statusTemperatureComponent,netFlotChartComponent } from "./assets/js/vue.helper.js"
|
|
import vue from "./assets/js/vue.build.js";
|
|
import mutationObserver from './assets/plugins/polyfill/mutationobserver.esm.js';
|
|
|
|
const { createApp,ref,reactive,onMounted } = vue;
|
|
const app = createApp({
|
|
components:{
|
|
"bs-switch":bootstrapSwitchComponent,
|
|
"net-chart":netFlotChartComponent,
|
|
"pie-chart":statusPieChartComponent,
|
|
"tmp-compt":statusTemperatureComponent
|
|
},
|
|
setup(prop,context){
|
|
|
|
const state = {
|
|
cpu: ref(0),
|
|
tmp: ref(0),
|
|
mem: ref(0),
|
|
maxy: ref(0),
|
|
tx : reactive([]),
|
|
rx : reactive([]),
|
|
data1:reactive([]),
|
|
data2:reactive([]),
|
|
theme_color:ref("#ffbb00"),
|
|
line2_color:ref("#555555"),
|
|
tipBorderColor:ref("#ffbb00"),
|
|
tipBgColor:ref("#ffffff"),
|
|
tipTxtColor:ref("#555555"),
|
|
tickColor:ref("#eeeeee"),
|
|
borderColor:ref("#cccccc"),
|
|
netFlotKey:ref(0),
|
|
preview : reactive([]),
|
|
input : reactive([]),
|
|
volume: reactive([])
|
|
}
|
|
|
|
const { defaultConf } = useDefaultConf();
|
|
|
|
const getData1 = (d) => {
|
|
state.data1.shift();
|
|
state.data1.push( d );
|
|
state.tx.splice(0);
|
|
for (let i = 0; i < 100; i++)
|
|
state.tx.push([i,state.data1[i]]);
|
|
}
|
|
|
|
const getData2 = (d) => {
|
|
state.data2.shift();
|
|
state.data2.push( d );
|
|
state.rx.splice(0);
|
|
for (let i = 0; i < 100; i++)
|
|
state.rx.push([i,state.data2[i]]);
|
|
}
|
|
|
|
const updateNetState = () => {
|
|
if(state.data1.length === 0 && state.data2.length === 0) {
|
|
for ( let i = 0; i < 100; i++ ) {
|
|
state.data1.push( 0 );
|
|
state.data2.push( 0 );
|
|
}
|
|
}
|
|
rpc("enc.getNetState").then(data => {
|
|
getData1(data.tx);
|
|
getData2(data.rx);
|
|
if ( data.tx * 1.3 > state.maxy.value )
|
|
state.maxy.value = data.tx * 1.3;
|
|
if ( data.rx * 1.3 > state.maxy.value )
|
|
state.maxy.value = data.rx * 1.3;
|
|
if ( state.maxy.value < 1024 )
|
|
state.maxy.value = Math.ceil( state.maxy.value / 100 ) * 100;
|
|
else
|
|
state.maxy.value = Math.ceil( state.maxy.value / 1024 ) * 1024;
|
|
if ( state.maxy.value > 1024000 )
|
|
state.maxy.value = 1024000;
|
|
setTimeout(updateNetState, 500);
|
|
});
|
|
}
|
|
|
|
const updateSysState = () => {
|
|
rpc("enc.getSysState").then(data => {
|
|
state.cpu.value = data.cpu;
|
|
state.mem.value = data.mem;
|
|
state.tmp.value = data.temperature;
|
|
setTimeout(updateSysState, 2000);
|
|
});
|
|
}
|
|
|
|
const makeImgUrl = (id) => {
|
|
return "snap/snap" + id + ".jpg?rnd=" + Math.random();
|
|
}
|
|
|
|
const updatePreview = () => {
|
|
if(state.preview.length === 0) {
|
|
for(let i=0;i<defaultConf.length;i++) {
|
|
if (!defaultConf[i].enable || ( defaultConf[i].type === "net" && !defaultConf[i].net.decodeV))
|
|
continue;
|
|
state.preview.push(defaultConf[i]);
|
|
}
|
|
}
|
|
setTimeout(() => rpc("enc.snap"),300);
|
|
setTimeout(updatePreview,800);
|
|
}
|
|
|
|
const handleChnVolume = (chnId,type) => {
|
|
let volume = state.volume.filter((item,index)=>{
|
|
return chnId === index;
|
|
})
|
|
let retVal = 0;
|
|
if(volume.length > 0)
|
|
retVal = volume[0][type] * 100/96;
|
|
return retVal + "%";
|
|
}
|
|
|
|
const updateVolume = () => {
|
|
rpc( "enc.getVolume").then(data => {
|
|
state.volume.splice(0,state.volume.length,...data);
|
|
});
|
|
|
|
if(window.location.host === "wx.linkpi.cn")
|
|
setTimeout(updateVolume,1000);
|
|
else
|
|
setTimeout(updateVolume,500);
|
|
}
|
|
|
|
const updateInputState = () => {
|
|
rpc("enc.getInputState").then(ret => {
|
|
state.input.splice(0, state.input.length, ...ret);
|
|
for(let i=0;i<state.input.length;i++) {
|
|
let ipt = state.input[i];
|
|
ipt.info = "- - -";
|
|
if(ipt.avalible)
|
|
ipt.info = "" + ipt.height + ( ipt.interlace ? "I" : "P" ) + ipt.framerate;
|
|
state.input[i] = ipt;
|
|
}
|
|
});
|
|
setTimeout(updateInputState,3000);
|
|
}
|
|
|
|
const onListenThemeChange = () => {
|
|
const html = document.querySelector('html');
|
|
const observer = new mutationObserver(mutations => {
|
|
mutations.forEach(mutation => {
|
|
if (mutation.type === 'attributes' && mutation.attributeName === "data-bs-theme") {
|
|
const theme = mutation.target.getAttribute("data-bs-theme");
|
|
if(theme === "default") {
|
|
state.tickColor.value = '#eee';
|
|
state.borderColor.value = '#ccc';
|
|
state.tipBgColor.value = '#fff';
|
|
state.tipBorderColor.value = '#fb0';
|
|
state.tipTxtColor.value = '#555';
|
|
state.line2_color.value = '#555';
|
|
}
|
|
if(theme === "dark") {
|
|
state.tickColor.value = '#555';
|
|
state.borderColor.value = '#555';
|
|
state.tipBgColor.value = '#333';
|
|
state.tipBorderColor.value = '#aaa';
|
|
state.tipTxtColor.value = '#adb5bd';
|
|
state.line2_color.value = '#999';
|
|
}
|
|
state.netFlotKey.value++;
|
|
}
|
|
});
|
|
});
|
|
const config = {
|
|
attributes: true,
|
|
attributeFilter: ["data-bs-theme"],
|
|
subtree: false
|
|
};
|
|
observer.observe(html, config);
|
|
}
|
|
|
|
onMounted(()=>{
|
|
updateSysState();
|
|
updateNetState();
|
|
updatePreview();
|
|
updateInputState();
|
|
updateVolume();
|
|
onListenThemeChange();
|
|
}
|
|
)
|
|
|
|
return {...state, makeImgUrl,handleChnVolume}
|
|
}
|
|
})
|
|
app.use(ignoreCustomElementPlugin);
|
|
app.use(filterKeywordPlugin);
|
|
app.mount('#app')
|
|
</script>
|
|
</body>
|
|
</html>
|