920 lines
53 KiB
PHP
920 lines
53 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 group" id="app" v-cloak>
|
|
<div class="row">
|
|
<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>Group list</en>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-lg-8">
|
|
<div class="row">
|
|
<div class="col-lg-2 lp-align-center">
|
|
<strong>
|
|
<cn>本机分组ID</cn>
|
|
<en>Group ID</en>
|
|
</strong>
|
|
</div>
|
|
<div class="col-lg-3">
|
|
<select v-if="Object.keys(groupConf).length > 0" class="form-select" v-model.number="groupConf.groupId">
|
|
<option value="0">0</option>
|
|
<option value="1">1</option>
|
|
<option value="2">2</option>
|
|
<option value="3">3</option>
|
|
<option value="4">4</option>
|
|
<option value="5">5</option>
|
|
<option value="6">6</option>
|
|
<option value="7">7</option>
|
|
<option value="8">8</option>
|
|
<option value="9">9</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-lg-7 p-0">
|
|
<button type="button" class="btn btn-primary border-3 me-2" @click="saveGroupConf">
|
|
<cn>保存</cn>
|
|
<en>Save</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3 me-2" @click="refreshGroupList">
|
|
<cn>同分组搜索</cn>
|
|
<en>Search again</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3" @click="showConfModal=!showConfModal">
|
|
<cn>同步配置</cn>
|
|
<en>Sync config</en>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="mt-3">
|
|
<table class="table table-striped mt-2">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">
|
|
<cn>序号</cn>
|
|
<en>Num</en>
|
|
</th>
|
|
<th class="text-center"> IP </th>
|
|
<th class="text-center"> Mac </th>
|
|
<th class="text-center">
|
|
<cn>设备型号</cn>
|
|
<en>Device</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>软件版本</cn>
|
|
<en>Version</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>频道</cn>
|
|
<en>Channels</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>操作</cn>
|
|
<en>Operation</en>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item,index) in groupList" :key="index">
|
|
<td class="text-center">{{index+1}}</td>
|
|
<td class="text-center">{{item.ip}}</td>
|
|
<td class="text-center">{{item.mac}}</td>
|
|
<td>{{item.type}}</td>
|
|
<td>
|
|
<div class="row" v-for="(it,idx) in Object.keys(item.version)" :key="idx">
|
|
<div class="col-lg-10 p-0">
|
|
<!-- <label class="text-truncate lp-align-right monospace">-->
|
|
<!-- {{it+'_'+item.version[it].split('_')[0]}}-->
|
|
<!-- </label>-->
|
|
<label class="text-truncate lp-align-right">
|
|
<span v-if="it==='app'">{{it}}</span>
|
|
<span v-if="it==='sdk'" style="letter-spacing: 0.05rem">{{it}}</span>
|
|
<span v-if="it==='sys'" style="letter-spacing: 0.1rem">{{it}}</span>
|
|
<span>{{'_'+item.version[it].split('_')[0]}}</span>
|
|
</label>
|
|
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="row" v-for="(it,idx) in splitArray(item.info,4)" :key="idx">
|
|
<div class="col-lg-12">
|
|
<label class="text-truncate">
|
|
{{it.join(' ,')}}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-lg-12 text-center">
|
|
<button class="btn btn-primary border-2 me-2" @click="changeDevNetwork(item.mac)">
|
|
<i class="fa-solid fa-network-wired"></i>
|
|
</button>
|
|
<button class="btn btn-primary border-2 me-2" @click="openDevUrl(item.ip)">
|
|
<i class="fa-brands fa-internet-explorer"></i>
|
|
</button>
|
|
<button class="btn btn-primary border-2" @click="rebootDev(item.mac)">
|
|
<i class="fa-solid fa-power-off"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<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>Channel collect</en>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-lg-8">
|
|
<div class="row">
|
|
<div class="col-lg-11 offset-lg-1 p-0">
|
|
<button type="button" class="btn btn-primary border-3 mx-2" @click="getEPG">
|
|
<cn>汇总</cn>
|
|
<en>Collect</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3 me-2" @click="createEPG">
|
|
<cn>生成节目单</cn>
|
|
<en>Create EPG</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3 me-2" @click="showSyncModal('epg')">
|
|
<cn>同步节目单</cn>
|
|
<en>Sync EPG</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3" @click="showEPG">
|
|
<cn>查看节目单</cn>
|
|
<en>Show EPG</en>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="mt-3">
|
|
<table class="table table-striped mt-2">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">
|
|
<cn>序号</cn>
|
|
<en>Num</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>频道名称</cn>
|
|
<en>Channel name</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>URL</cn>
|
|
<en>URL</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>排序</cn>
|
|
<en>Sequence</en>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-if="epgList.length > 0" v-for="(item,index) in epgList" :key="index">
|
|
<td class="text-center">{{index+1}}</td>
|
|
<td class="text-center">{{item.name}}</td>
|
|
<td class="text-center">{{item.url}}</td>
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-lg-12 text-center">
|
|
<button type="button" class="btn btn-primary border-3 me-2" @click="epgSwap(index,'top')">
|
|
<cn>置顶</cn>
|
|
<en>Top</en>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3 px-3 me-2" @click="epgSwap(index,'up')">
|
|
<i class="fa-solid fa-arrow-up"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3 px-3 me-2" @click="epgSwap(index,'down')">
|
|
<i class="fa-solid fa-arrow-down"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-primary border-3" @click="epgSwap(index,'buttom')">
|
|
<cn>置底</cn>
|
|
<en>Button</en>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<group-modal :modal-title="'群组设置同步&Group config sync'" :modal-show="showGroupModal"
|
|
:confirm-btn-name="'同步&Sync'" :modal-size="'modal-lg'" @confirm-btn-click="syncGroupEPG" @modal-visible="groupModalVisible">
|
|
<table class="table table-striped mt-2">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">
|
|
<cn>IP</cn>
|
|
<en>IP</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>设备型号</cn>
|
|
<en>Device</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>Mac</cn>
|
|
<en>Mac</en>
|
|
</th>
|
|
<th class="text-center">
|
|
<cn>Status</cn>
|
|
<en>Status</en>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-if="groupList.length > 0" v-for="(item,index) in groupList" :key="index">
|
|
<td class="text-center">{{item.ip}}</td>
|
|
<td class="text-center">{{item.type}}</td>
|
|
<td class="text-center">{{item.mac}}</td>
|
|
<td class="text-center">
|
|
<i v-if="!item.status || item.status === 0" class="fa-solid fa-ellipsis"></i>
|
|
<i v-if="item.status === 1" class="fa-solid text-success fa-check"></i>
|
|
<i v-if="item.status === 2" class="fa-solid text-danger fa-xmark"></i>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</group-modal>
|
|
<conf-modal :modal-title="'参数设置&Config'" :modal-show="showConfModal" :had-footer="false"
|
|
:confirm-btn-name="'同步配置&Sync config'" :modal-size="'modal-xl'" @confirm-btn-click="showGroupModal =! showGroupModal">
|
|
<div class="row">
|
|
<div class="col-lg-12" v-if="Object.keys(globalConf).length !== 0">
|
|
<div class="row">
|
|
<div class="col-1"></div>
|
|
<div class="col-11">
|
|
<div class="row">
|
|
<div class="col-2 text-center">
|
|
<cn>分辨率</cn>
|
|
<en>video size</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>编码方式</cn>
|
|
<en>codec</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>码率控制</cn>
|
|
<en>rate control</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>码率(kb/s)</cn>
|
|
<en>bitrate(kb/s)</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>帧率</cn>
|
|
<en>framerate</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>GOP(秒)</cn>
|
|
<en>GOP(sec)</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>帧同步</cn>
|
|
<en>Sync</en>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-1">
|
|
<div class="col-1 text-center p-0 pt-2">
|
|
<cn>主流参数</cn>
|
|
<en>Main stream</en>
|
|
</div>
|
|
<div class="col-11">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
<multiple-select v-model:value1="globalConf.encv.width" v-model:value2="globalConf.encv.height" split="x">
|
|
<option value="-1x-1">auto</option>
|
|
<option v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.capability.encode.maxSize === '4k'" value="3840x2160">4K</option>
|
|
<option value="1920x1080">1080p</option>
|
|
<option value="1280x720">720p</option>
|
|
<option value="640x360">360p</option>
|
|
<option value="1080x1920">1080x1920</option>
|
|
<option value="720x1280">720x1280</option>
|
|
<option value="360x640">360x640</option>
|
|
</multiple-select>
|
|
</div>
|
|
<div class="col-2">
|
|
<multiple-select v-model:value1="globalConf.encv.codec" v-model:value2="globalConf.encv.profile" split=",">
|
|
<option value="h264,base">H.264 Base</option>
|
|
<option value="h264,main">H.264 Main</option>
|
|
<option value="h264,high">H.264 High</option>
|
|
<option value="h265,main">H.265 Main</option>
|
|
<option value="close,base">Close</option>
|
|
</multiple-select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.encv.rcmode">
|
|
<option value="cbr">CBR</option>
|
|
<option value="vbr">VBR</option>
|
|
<option value="avbr">AVBR</option>
|
|
<option value="fixqp">FIXQP</option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv.bitrate">
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv.framerate">
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv.gop">
|
|
</div>
|
|
<div class="col">
|
|
<multiple-select v-model:value1="globalConf.encv.syncTS" v-model:value2="globalConf.encv.syncTSMode" split=",">
|
|
<option cn="芯象" en="Sinsam" value="true,sinsam" v-language-option></option>
|
|
<option cn="简易" en="Normal" value="true,linkpi" v-language-option></option>
|
|
<option cn="关闭" en="Close" value="false,linkpi" v-language-option></option>
|
|
</multiple-select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-1">
|
|
<div class="col-1 text-center p-0 pt-2">
|
|
<cn>辅流参数</cn>
|
|
<en>Sub stream</en>
|
|
</div>
|
|
<div class="col-11">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
<multiple-select v-model:value1="globalConf.encv2.width" v-model:value2="globalConf.encv2.height" split="x">
|
|
<option value="-1x-1">auto</option>
|
|
<option v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.capability.encode.maxSize === '4k'" value="3840x2160">4K</option>
|
|
<option value="1920x1080">1080p</option>
|
|
<option value="1280x720">720p</option>
|
|
<option value="640x360">360p</option>
|
|
<option value="1080x1920">1080x1920</option>
|
|
<option value="720x1280">720x1280</option>
|
|
<option value="360x640">360x640</option>
|
|
</multiple-select>
|
|
</div>
|
|
<div class="col-2">
|
|
<multiple-select v-model:value1="globalConf.encv2.codec" v-model:value2="globalConf.encv2.profile" split=",">
|
|
<option value="h264,base">H.264 Base</option>
|
|
<option value="h264,main">H.264 Main</option>
|
|
<option value="h264,high">H.264 High</option>
|
|
<option value="h265,main">H.265 Main</option>
|
|
<option value="close,base">Close</option>
|
|
</multiple-select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.encv2.rcmode">
|
|
<option value="cbr">CBR</option>
|
|
<option value="vbr">VBR</option>
|
|
<option value="avbr">AVBR</option>
|
|
<option value="fixqp">FIXQP</option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv2.bitrate">
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv2.framerate">
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.encv2.gop">
|
|
</div>
|
|
<div class="col">
|
|
<multiple-select v-model:value1="globalConf.encv2.syncTS" v-model:value2="globalConf.encv2.syncTSMode" split=",">
|
|
<option cn="芯象" en="Sinsam" value="true,sinsam" v-language-option></option>
|
|
<option cn="简易" en="Normal" value="true,linkpi" v-language-option></option>
|
|
<option cn="关闭" en="Close" value="false,linkpi" v-language-option></option>
|
|
</multiple-select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr >
|
|
<div class="row">
|
|
<div class="col-1"></div>
|
|
<div class="col-11">
|
|
<div class="row">
|
|
<div class="col-2 text-center">
|
|
<cn>编码格式</cn>
|
|
<en>codec</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>音源</cn>
|
|
<en>source</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>增益</cn>
|
|
<en>gain</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>采样率</cn>
|
|
<en>samplerate</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>声道</cn>
|
|
<en>channels</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>码率(kb/s)</cn>
|
|
<en>bitrate(kb/s)</en>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-1">
|
|
<div class="col-1 text-center p-0 pt-2">
|
|
<cn>音频参数</cn>
|
|
<en>Audio config</en>
|
|
</div>
|
|
<div class="col-11">
|
|
<div class="row">
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.enca.codec">
|
|
<option value="aac">AAC</option>
|
|
<option value="pcma">PCMA</option>
|
|
<option value="mp2">MPEG2</option>
|
|
<option value="mp3">MP3</option>
|
|
<option value="close" cn="关闭" en="close" v-language-option></option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.enca.audioSrc">
|
|
<option value="source">Default</option>
|
|
<option v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.function.line" value="line">Line</option>
|
|
<option v-for="(item,index) in defaultConf" :key="index" :value="item.id">{{item.name}}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.enca.gain">
|
|
<option value="24">+24dB</option>
|
|
<option value="18">+18dB</option>
|
|
<option value="12">+12dB</option>
|
|
<option value="6">+6dB</option>
|
|
<option value="0">+0dB</option>
|
|
<option value="-6">-6dB</option>
|
|
<option value="-12">-12dB</option>
|
|
<option value="-18">-18dB</option>
|
|
<option value="-24">-24dB</option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.enca.samplerate">
|
|
<option value="-1">auto</option>
|
|
<option value="16000">16K</option>
|
|
<option value="32000">32K</option>
|
|
<option value="44100">44.1K</option>
|
|
<option value="48000">48K</option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<select class="form-select" v-model="globalConf.enca.channels">
|
|
<option cn="单声道" en="mono" value="1" v-language-option></option>
|
|
<option cn="立体声" en="stereo" value="2" v-language-option></option>
|
|
</select>
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model.trim.lazy="globalConf.enca.bitrate">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr >
|
|
<div class="row text-center mt-3">
|
|
<div class="col-lg-12">
|
|
<button type="button" class="btn border-3 btn-primary me-2" @click="showSyncModal('enc')">
|
|
<cn>应用到群组</cn>
|
|
<en>Apply to all/en>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<hr class="my-4">
|
|
<div class="row">
|
|
<div class="col-lg-12" v-if="Object.keys(globalConf).length !== 0">
|
|
<div class="row">
|
|
<div class="col"></div>
|
|
<div class="col text-center">
|
|
HTTP
|
|
</div>
|
|
<div v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.function.hls" class="col text-center">
|
|
HLS
|
|
</div>
|
|
<div class="col text-center">
|
|
RTMP
|
|
</div>
|
|
<div class="col text-center">
|
|
RTSP
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>组播</cn>
|
|
<en>multicast</en>
|
|
</div>
|
|
<div class="col-2 text-center">
|
|
<cn>组播地址</cn>
|
|
<en>multicast addr</en>
|
|
</div>
|
|
<div class="col text-center">
|
|
<cn>推流</cn>
|
|
<en>push</en>
|
|
</div>
|
|
<div class="col"></div>
|
|
</div>
|
|
<div class="row mt-1">
|
|
<div class="col text-center p-0 pt-2">
|
|
<cn>主流协议</cn>
|
|
<en>Main protocol</en>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.http" ></bs-switch>
|
|
</div>
|
|
<div v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.function.hls" class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.hls" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.rtmp" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.rtsp.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.udp.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col-2">
|
|
<multiple-input type="text" class="form-control" v-model:value1="globalConf.stream.udp.ip" v-model:value2="globalConf.stream.udp.port" split=":"></multiple-input>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream.push.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col"></div>
|
|
</div>
|
|
|
|
<div class="row mt-2">
|
|
<div class="col text-center p-0 pt-2">
|
|
<cn>辅流协议</cn>
|
|
<en>Sub protocol</en>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.http" ></bs-switch>
|
|
</div>
|
|
<div v-if="Object.keys(hardwareConf).length > 0 && hardwareConf.function.hls" class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.hls" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.rtmp" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.rtsp.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.udp.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col-2">
|
|
<multiple-input type="text" class="form-control" v-model:value1="globalConf.stream2.udp.ip" v-model:value2="globalConf.stream2.udp.port" split=":"></multiple-input>
|
|
</div>
|
|
<div class="col lp-align-center">
|
|
<bs-switch v-model="globalConf.stream2.push.enable" ></bs-switch>
|
|
</div>
|
|
<div class="col"></div>
|
|
</div>
|
|
<hr class="mt-3 mb-5">
|
|
<div class="row text-center">
|
|
<div class="col-lg-12">
|
|
<button type="button" class="btn border-3 btn-primary me-2" @click="showSyncModal('stream')">
|
|
<cn>应用到群组</cn>
|
|
<en>Apply to all/en>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</conf-modal>
|
|
</main>
|
|
</div>
|
|
<?php include ("./public/foot.inc") ?>
|
|
|
|
<script type="module">
|
|
import {rpc, alertMsg, splitArray, confirm, clearReactiveArray, isEmpty, deepCopy} from "./assets/js/lp.utils.js";
|
|
import { useDefaultConf,useGroupConf,useHardwareConf } from "./assets/js/vue.hooks.js";
|
|
import { ignoreCustomElementPlugin,multipleSelectComponent,multipleInputComponent,
|
|
languageOptionDirective,bootstrapSwitchComponent,customModalComponent } from "./assets/js/vue.helper.js"
|
|
import vue from "./assets/js/vue.build.js";
|
|
|
|
const {createApp,ref,reactive,watch,onMounted,nextTick} = vue;
|
|
const app = createApp({
|
|
directives:{
|
|
"language-option": languageOptionDirective
|
|
},
|
|
components:{
|
|
"bs-switch" : bootstrapSwitchComponent,
|
|
"multiple-select": multipleSelectComponent,
|
|
"multiple-input": multipleInputComponent,
|
|
"group-modal":customModalComponent,
|
|
"conf-modal":customModalComponent
|
|
},
|
|
setup(props,context) {
|
|
|
|
const { defaultConf } = useDefaultConf();
|
|
const { groupConf,updateGroupConf } = useGroupConf();
|
|
const { hardwareConf } = useHardwareConf();
|
|
|
|
const state = {
|
|
groupList: reactive([]),
|
|
intervalId: ref(-1),
|
|
epgList: reactive([]),
|
|
showGroupModal: ref(false),
|
|
showConfModal: ref(false),
|
|
syncType:ref(""),
|
|
globalConf: reactive({})
|
|
}
|
|
|
|
const unwatch = watch(defaultConf, (value) => {
|
|
Object.assign(state.globalConf, defaultConf[0]);
|
|
state.globalConf.stream.udp.port += "+";
|
|
state.globalConf.stream2.udp.port += "+";
|
|
unwatch();
|
|
});
|
|
|
|
const getGroupList = () => {
|
|
rpc("group.getList").then( data => {
|
|
state.groupList.splice(0);
|
|
state.groupList.push(...data);
|
|
} );
|
|
setTimeout(getGroupList,3000);
|
|
}
|
|
|
|
const refreshGroupList = () => {
|
|
rpc("group.clearMember")
|
|
.then(result => {
|
|
return new Promise((resolve,reject) => {
|
|
if(result)
|
|
resolve();
|
|
else
|
|
reject();
|
|
});
|
|
})
|
|
.then(()=>{
|
|
clearInterval(state.intervalId.value);
|
|
let count = 0;
|
|
state.intervalId.value = setInterval(()=>{
|
|
rpc("group.getList").then( data => {
|
|
count++;
|
|
clearReactiveArray(state.groupList);
|
|
state.groupList.push(...data);
|
|
if(count === 10)
|
|
clearInterval(state.intervalId.value);
|
|
} );
|
|
},1000);
|
|
});
|
|
}
|
|
|
|
const saveGroupConf = () => {
|
|
updateGroupConf().then(()=>{
|
|
clearInterval(state.intervalId.value);
|
|
})
|
|
}
|
|
|
|
const changeDevNetwork = async mac => {
|
|
const network = await rpc("group.callGetNetwork",[mac]);
|
|
const jc = confirm({
|
|
title: '<cn>网络设置</cn><en>Layout</en>',
|
|
content: `<div class="row mt-3">
|
|
<div class="col-lg-4 text-center">
|
|
<cn>IP</cn>
|
|
<en>IP</en>
|
|
</div>
|
|
<div class="col-lg-7">
|
|
<input type="text" class="form-control" id="net_ipaddr">
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-lg-4 text-center">
|
|
<cn>掩码</cn>
|
|
<en>Mask</en>
|
|
</div>
|
|
<div class="col-lg-7">
|
|
<input type="text" class="form-control" id="net_mask">
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-lg-4 text-center">
|
|
<cn>网关</cn>
|
|
<en>Gateway</en>
|
|
</div>
|
|
<div class="col-lg-7">
|
|
<input type="text" class="form-control" id="net_gateway">
|
|
</div>
|
|
</div>`,
|
|
buttons: {
|
|
ok: {
|
|
text: "<cn>保存</cn><en>Confirm</en>",
|
|
btnClass: 'btn-primary',
|
|
keys: ['enter'],
|
|
action: () => {
|
|
network.ip = document.querySelector('#net_ipaddr').value;
|
|
network.mask = document.querySelector('#net_mask').value;
|
|
network.gateway = document.querySelector('#net_gateway').value;
|
|
rpc( "group.callSetNetwork", [mac, network]).then(ret => {
|
|
if(ret) {
|
|
jc.close();
|
|
alertMsg("<cn>设置成功</cn><en>Setting successfully</en>","success");
|
|
return;
|
|
}
|
|
alertMsg("<cn>设置失败</cn><en>Setting failed</en>","success");
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
cancel: {
|
|
text: "<cn>取消</cn><en>Cancel</en>",
|
|
action: () => {}
|
|
}
|
|
},
|
|
onOpenBefore:()=>{
|
|
document.querySelector('#net_ipaddr').value = network.ip;
|
|
document.querySelector('#net_mask').value = network.mask;
|
|
document.querySelector('#net_gateway').value = network.gateway;
|
|
}
|
|
});
|
|
}
|
|
|
|
const openDevUrl = ipAddr => {
|
|
window.open("http://"+ipAddr+"/");
|
|
}
|
|
|
|
const rebootDev = mac => {
|
|
confirm( {
|
|
title: '<cn>重启</cn><en>Reboot</en>',
|
|
content: '<cn>是否立即重启系统?</cn><en>Reboot immediately?</en>',
|
|
buttons: {
|
|
ok: {
|
|
text: "<cn>确认重启</cn><en>Confirm</en>",
|
|
btnClass: 'btn-primary',
|
|
keys: [ 'enter' ],
|
|
action: () => rpc( "group.callReboot", [mac])
|
|
},
|
|
cancel: {
|
|
text: "<cn>取消</cn><en>Cancel</en>",
|
|
action: () => { }
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
const getEPG = () => {
|
|
rpc("group.callGetEPG").then(data => {
|
|
clearReactiveArray(state.epgList);
|
|
state.epgList.push(...data);
|
|
});
|
|
}
|
|
|
|
const epgSwap = (index,type) => {
|
|
if(type === "top") {
|
|
const item = state.epgList.splice(index, 1)[0];
|
|
state.epgList.unshift(item);
|
|
}
|
|
|
|
if(type === "up") {
|
|
if(index > 0) {
|
|
const item = state.epgList.splice(index, 1)[0];
|
|
state.epgList.splice(index - 1, 0, item);
|
|
}
|
|
}
|
|
|
|
if(type === "down") {
|
|
if (index < state.epgList.length - 1) {
|
|
const item = state.epgList.splice(index, 1)[0];
|
|
state.epgList.splice(index + 1, 0, item);
|
|
}
|
|
}
|
|
|
|
if(type === "buttom") {
|
|
const item = state.epgList.splice(index, 1)[0];
|
|
state.epgList.push(item);
|
|
}
|
|
|
|
rpc("group.orderEPG", [state.epgList]).then( data => {
|
|
console.log(data,"#######");
|
|
// clearReactiveArray(state.epgList);
|
|
// state.epgList.push(...data);
|
|
} );
|
|
}
|
|
|
|
const createEPG = () => {
|
|
rpc("group.createEPG").then( data => {
|
|
if(data)
|
|
alertMsg("<cn>生成节目单成功</cn><en>Create EPG success</en>","success");
|
|
} );
|
|
}
|
|
|
|
const showEPG = () => window.open("config/epg.json")
|
|
|
|
const showSyncModal = type =>{
|
|
state.syncType.value = type;
|
|
state.groupList.forEach(item => item.status = 0);
|
|
state.showGroupModal.value = !state.showGroupModal.value;
|
|
}
|
|
|
|
const updateMulticastAddress = (address, increment) => {
|
|
let parts = address.split(".");
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
let num = parseInt(parts[i]);
|
|
num += increment;
|
|
if (num > 255) {
|
|
parts[i] = num % 256;
|
|
increment = Math.floor(num / 256);
|
|
} else {
|
|
parts[i] = num;
|
|
break;
|
|
}
|
|
}
|
|
return parts.join(".");
|
|
}
|
|
|
|
const syncGroupEPG = () => {
|
|
state.groupList.forEach(item => item.status = 0);
|
|
state.groupList.forEach((item,index) => {
|
|
setTimeout(()=> {
|
|
if(state.syncType.value === 'epg') {
|
|
rpc("group.callSyncEPG", [item.mac]).then(ret => {
|
|
//item.status = ret ? 1 : 2;
|
|
item.status = 1;
|
|
});
|
|
}
|
|
|
|
if(state.syncType.value === 'enc') {
|
|
rpc("group.callSetEncode", [item.mac, state.globalConf]).then( ret => {
|
|
//item.status = ret ? 1 : 2;
|
|
item.status = 1;
|
|
});
|
|
}
|
|
|
|
if(state.syncType.value === 'stream') {
|
|
const conf = deepCopy(state.globalConf);
|
|
const streams = ['stream', 'stream2'];
|
|
streams.forEach((stream) => {
|
|
delete conf[stream].suffix;
|
|
if (conf[stream].udp.ip.includes("+")) {
|
|
let ip = conf[stream].udp.ip.replace(/[+\s]/g, '');
|
|
ip = updateMulticastAddress(ip, index * 20);
|
|
conf[stream].udp.ip = ip;
|
|
}
|
|
if (conf[stream].udp.port.includes("+")) {
|
|
let port = Number(conf[stream].udp.port.replace(/[+\s]/g, ''));
|
|
conf[stream].udp.port = isNaN(port) ? (3000 + index * 20) : (port + index * 20);
|
|
conf[stream].udp.port += "+";
|
|
}
|
|
});
|
|
rpc("group.callSetStream", [item.mac, {"cfg": conf.stream, "cfg2": conf.stream2}]).then(ret => {
|
|
//item.status = ret ? 1 : 2;
|
|
item.status = 1;
|
|
});
|
|
}
|
|
|
|
},index*300)
|
|
});
|
|
}
|
|
|
|
const groupModalVisible = visible => {
|
|
if(isEmpty(state.syncType.value) || state.syncType.value === "epg")
|
|
return;
|
|
state.showConfModal.value = !visible;
|
|
}
|
|
|
|
onMounted(refreshGroupList);
|
|
|
|
return {...state,defaultConf,hardwareConf,splitArray,groupConf,saveGroupConf,refreshGroupList,changeDevNetwork,openDevUrl,
|
|
rebootDev,getEPG,epgSwap,createEPG,showEPG,syncGroupEPG,showSyncModal,groupModalVisible}
|
|
}
|
|
});
|
|
app.use(ignoreCustomElementPlugin);
|
|
app.mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|