terminal-web-server/static/main-scpc.html

1686 lines
86 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSCM-101</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/fields.css">
<style>
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: var(--bg-selected);
}
body { /* значение по-умолчанию */ --header-height: 60px; }
#content {
padding-top: var(--header-height);
}
.l3-proto-label {
margin: 0 0 0 0.5em;
}
.l3-proto-label > * {
display: inline-block;
}
.l3-proto-label input[type=checkbox] {
width: auto;
}
</style>
</head>
<body>
<div id="app" hidden>
<header>
<span class="nav-bar-element">Прием: <span :class="{ indicator_bad: stat_rx.state === false, indicator_good: stat_rx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Передача: <span :class="{ indicator_good: stat_tx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Тест: <span :class="{ indicator_good: (param.general.isTestInputData === true || param.general.modulatorMode === 'test'), indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
<span :class="{ value_bad: initState !== 'Успешная инициализация системы' }">{{ initState }}</span>
<div class="tabs-header">
<span style="font-weight:bold">RSCM-101</span>
<a href="#monitoring" class="tabs-btn" @click="activeTab = 'monitoring'" :class="{ active: activeTab === 'monitoring' }">Мониторинг</a>
<a href="#setup" class="tabs-btn" @click="activeTab = 'setup'" :class="{ active: activeTab === 'setup' }">Настройки</a>
<a href="#qos" class="tabs-btn" @click="activeTab = 'qos'" :class="{ active: activeTab === 'qos' }">QoS</a>
<a href="#admin" class="tabs-btn" @click="activeTab = 'admin'" :class="{ active: activeTab === 'admin' }">Администрирование</a>
<a href="/logout" class="tabs-btn">Выход</a>
</div>
</header>
<div id="content">
<div class="tabs-body-item tabs-item-flex-container" v-if="activeTab === 'monitoring'">
<div class="settings-set-container">
<h2>Статистика приема</h2>
<table>
<tbody>
<tr><th>Прием</th><td><span :class="{ indicator_bad: stat_rx.state === false, indicator_good: stat_rx.state === true, indicator: true }"></span></td></tr>
<tr><th>Захват символьной</th><td><span :class="{ indicator_bad: stat_rx.sym_sync_lock === false, indicator_good: stat_rx.sym_sync_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват ФАПЧ</th><td><span :class="{ indicator_bad: stat_rx.afc_lock === false, indicator_good: stat_rx.afc_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват поиска по частоте</th><td><span :class="{ indicator_bad: stat_rx.freq_search_lock === false, indicator_good: stat_rx.freq_search_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват пакетной синхр.</th><td><span :class="{ indicator_bad: stat_rx.pkt_sync === false, indicator_good: stat_rx.pkt_sync === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ stat_rx.snr }} / {{ stat_rx.rssi }}</td></tr>
<tr><th>Modcod</th><td>{{ stat_rx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ stat_rx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ stat_rx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Символьная ошибка</th><td>{{ stat_rx.symError }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ stat_rx.freqErr }} / {{ stat_rx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ stat_rx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ stat_rx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ stat_rx.speedOnRxKbit }} kbit/s</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ stat_rx.speedOnIifKbit }} kbit/s</td></tr>
</tbody>
</table>
<p> Статистика пакетов </p>
<table>
<tbody>
<tr><th>Качественных пакетов</th><td>{{ stat_rx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ stat_rx.packetsBad }}</td></tr>
<tr><th>DUMMY</th><td>{{ stat_rx.packetsDummy }}</td></tr>
</tbody>
</table>
<button class="action-button" @click="resetPacketsStatistics()"> Сброс статистики </button>
</div>
<div class="settings-set-container">
<h2>Статистика передачи</h2>
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: stat_tx.state === false, indicator_good: stat_tx.state === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ дальнего приема</th><td>{{ stat_tx.snr }}</td></tr>
<tr><th>Modcod</th><td>{{ stat_tx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ stat_tx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ stat_tx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ stat_tx.speedOnTxKbit }} kbit/s</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ stat_tx.speedOnIifKbit }} kbit/s</td></tr>
</tbody>
</table>
</div>
<div class="settings-set-container" v-if="isCinC === true">
<h2>Статистика режима CinC</h2>
<table>
<tbody>
<tr><th>ОСС</th><td>{{ stat_cinc.occ }}</td></tr>
<tr><th>Захват коррелятора</th><td><span :class="{ indicator_bad: stat_cinc.correlator === false, indicator_good: stat_cinc.correlator === true, indicator: true }"></span></td></tr>
<tr><th>Кол-во срывов коррелятора</th><td>{{ stat_cinc.correlatorFails }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ stat_cinc.freqErr }} / {{ stat_cinc.freqErrAcc }}</td></tr>
<tr><th>Задержка в канале, мс</th><td>{{ stat_cinc.channelDelay }}</td></tr>
</tbody>
</table>
</div>
<div class="settings-set-container">
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ stat_device.adrv }} °C</td></tr>
<tr><th>Температура ZYNQ</th><td>{{ stat_device.zynq }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ stat_device.fpga }} °C</td></tr>
</tbody>
</table>
</div>
</div>
<div class="tabs-body-item" v-if="activeTab === 'setup' && settingFetchComplete">
<h2>Настройки приема/передачи</h2>
<div class="settings-set-container">
<label>
<span>Режим работы</span>
<select v-model="param.general.isCinC">
<option :value="false">SCPC</option>
<option :value="true">CinC</option>
</select>
</label>
</div>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки передатчика</h3>
<label>
<span>Включить передатчик</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.general.txEn" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Автоматический запуск передатчика</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.general.autoStartTx" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Режим работы модулятора</span>
<select v-model="param.general.modulatorMode">
<option value="normal">Нормальный</option>
<option value="test">Тест (CW)</option>
</select>
</label>
<label>
<span>Входные данные</span>
<select v-model="param.general.isTestInputData">
<option :value="false">Ethernet</option>
<option :value="true">Тест</option>
</select>
</label>
<h3>Параметры передачи</h3>
<label>
<span>Центральная частота, КГц</span>
<input v-model="param.tx.centerFreq" type="number" step="0.01"/>
</label>
<label>
<span>Символьная скорость, Бод</span>
<input v-model="param.tx.cymRate" type="number"/>
</label>
<label>
<span>Roll-off</span>
<select v-model="param.tx.rolloff">
<option :value="2">0.02</option>
<option :value="5">0.05</option>
<option :value="10">0.10</option>
<option :value="15">0.15</option>
<option :value="20">0.20</option>
<option :value="25">0.25</option>
</select>
</label>
<label>
<span>Ослабление, дБ</span>
<input v-model="param.tx.attenuation" type="number" step="0.01"/>
</label>
</div>
<div class="settings-set-container">
<h3>Режим работы DVB-S2</h3>
<label>
<span>Период служебных пакетов, сек</span>
<input v-model="param.dvbs2.servicePacketPeriod" type="number">
</label>
<label>
<span>Режим</span>
<select v-model="param.dvbs2.mode">
<option value="ccm">CCM</option>
<option value="acm">ACM</option>
</select>
</label>
<label>
<span>Размер кадра</span>
<select v-model="param.dvbs2.frameSizeNormal">
<option :value="true">normal</option>
<option :value="false">short</option>
</select>
</label>
<!-- <label>-->
<!-- <span>Пилот-символы</span>-->
<!-- <select v-model="param.dvbs2.isPilots">-->
<!-- <option :value="true">pilots</option>-->
<!-- <option :value="false">no pilots</option>-->
<!-- </select>-->
<!-- </label>-->
<label v-show="param.dvbs2.mode === 'ccm'">
<span>Модуляция</span>
<select v-model="param.dvbs2.ccm_modulation" @change="param.dvbs2.ccm_speed = correctModcodSpeed(param.dvbs2.ccm_modulation, param.dvbs2.ccm_speed)">
<option value="qpsk">QPSK</option>
<option value="8psk">8PSK</option>
<option value="16apsk">16APSK</option>
<option value="32apsk">32APSK</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'ccm'">
<span>Скорость кода</span>
<select v-model="param.dvbs2.ccm_speed">
<option v-for="speed in getAvailableModcods(param.dvbs2.ccm_modulation)" v-bind:value="speed">{{ speed }}</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Текущий модкод</span>
<input v-model="stat_rx.modcod" readonly>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Модуляция (макс. режим)</span>
<select v-model="param.dvbs2.acm_maxModulation" @change="param.dvbs2.acm_maxSpeed = correctModcodSpeed(param.dvbs2.acm_maxModulation, param.dvbs2.acm_maxSpeed)">
<option value="qpsk">QPSK</option>
<option value="8psk">8PSK</option>
<option value="16apsk">16APSK</option>
<option value="32apsk">32APSK</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Скорость кода (макс. режим)</span>
<select v-model="param.dvbs2.acm_maxSpeed">
<option v-for="speed in getAvailableModcods(param.dvbs2.acm_maxModulation)" v-bind:value="speed">{{ speed }}</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Модуляция (мин. режим)</span>
<select v-model="param.dvbs2.acm_minModulation" @change="param.dvbs2.acm_minSpeed = correctModcodSpeed(param.dvbs2.acm_minModulation, param.dvbs2.acm_minSpeed)">
<option value="qpsk">QPSK</option>
<option value="8psk">8PSK</option>
<option value="16apsk">16APSK</option>
<option value="32apsk">32APSK</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Скорость кода (мин. режим)</span>
<select v-model="param.dvbs2.acm_minSpeed">
<option v-for="speed in getAvailableModcods(param.dvbs2.acm_minModulation)" v-bind:value="speed">{{ speed }}</option>
</select>
</label>
<label v-show="param.dvbs2.mode === 'acm'">
<span>Запас ОСШ</span>
<input v-model="param.dvbs2.snrReserve" type="number" step="0.01">
</label>
<h3>Авто-регулировка мощности</h3>
<label>
<span>Авто-регулировка мощности</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.acm.en" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Максимальное ослабление</span>
<input v-model="param.acm.maxAttenuation" type="number" step="0.01"/>
</label>
<label>
<span>Минимальное ослабление</span>
<input v-model="param.acm.minAttenuation" type="number" step="0.01"/>
</label>
<label>
<span>Требуемое ОСШ</span>
<input v-model="param.acm.requiredSnr" type="number" step="0.01"/>
</label>
</div>
<div class="settings-set-container">
<h3>Настройка приемника</h3>
<label>
<span>Режим управления усилением</span>
<select v-model="param.rx.gainMode">
<option value="auto">АРУ</option>
<option value="manual">РРУ</option>
</select>
</label>
<label v-show="param.rx.gainMode === 'manual'">
<span>Усиление, dB</span>
<input v-model="param.rx.manualGain" type="number"/>
</label>
<label>
<span>Инверсия спектра</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.rx.spectrumInversion" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Центральная частота, кГц</span>
<input v-model="param.rx.centerFreq" type="number" step="0.01"/>
</label>
<label>
<span>Символьная скорость, Бод</span>
<input v-model="param.rx.cymRate" type="number"/>
</label>
<label>
<span>Roll-off</span>
<select v-model="param.rx.rolloff">
<option :value="2">0.02</option>
<option :value="5">0.05</option>
<option :value="10">0.10</option>
<option :value="15">0.15</option>
<option :value="20">0.20</option>
<option :value="25">0.25</option>
</select>
</label>
</div>
</div>
<button class="action-button" @click="settingsSubmitRxTx()">Сохранить <span class="submit-spinner" v-show="submitStatus.rxTx"></span></button>
<h2 v-show="param.general.isCinC === true">Настройки режима CinC</h2>
<div v-show="param.general.isCinC === true" class="settings-set-container">
<label>
<span>Метод расчета задержки</span>
<select v-model="param.cinc.mode">
<option value="positional">Позиционированием</option>
<option value="delay">Окном задержки</option>
</select>
</label>
<label>
<span>Полоса поиска, кгц ±</span>
<input v-model="param.cinc.searchBandwidth" type="number" min="0" max="100" step="1"/>
</label>
<h3 v-show="param.cinc.mode === 'positional'">Настройки позиционирования</h3>
<label v-show="param.cinc.mode === 'positional'">
<span>Широта станции</span>
<input v-model="param.cinc.position.station.latitude" type="number" step="0.000001"/>
</label>
<label v-show="param.cinc.mode === 'positional'">
<span>Долгота станции</span>
<input v-model="param.cinc.position.station.longitude" type="number" step="0.000001"/>
</label>
<label v-show="param.cinc.mode === 'positional'">
<span>Подспутниковая точка</span>
<input v-model="param.cinc.position.satelliteLongitude" type="number" step="0.000001"/>
</label>
<h3 v-show="param.cinc.mode === 'delay'">Задержка до спутника</h3>
<label v-show="param.cinc.mode === 'delay'">
<span>от, мс</span>
<input v-model="param.cinc.delayMin" type="number"/>
</label>
<label v-show="param.cinc.mode === 'delay'">
<span>до, мс</span>
<input v-model="param.cinc.delayMax" type="number"/>
</label>
</div>
<button class="action-button" v-show="param.general.isCinC" @click="settingsSubmitCinC()" type="submit">Сохранить <span class="submit-spinner" v-show="submitStatus.cinc"></span></button>
<h2>Настройки питания и опорного генератора</h2>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки BUC</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.buc.refClk10M" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Питание BUC</span>
<select v-model="param.buc.powering">
<option value="0">выкл</option>
<option value="24">24В</option>
<option value="48">48В</option>
</select>
</label>
</div>
<div class="settings-set-container">
<h3>Настройки LNB</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.lnb.refClk10M" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Питание LNB</span>
<select v-model="param.lnb.powering">
<option value="0">выкл</option>
<option value="13">13В</option>
<option value="18">18В</option>
<option value="24">24В</option>
</select>
</label>
</div>
<div class="settings-set-container">
<h3>Сервисные настройки</h3>
<label>
<span>Подача опоры 10МГц на 'Выход 10МГц'</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.serviceSettings.refClk10M" />
<span class="slider"></span>
</span>
</label>
<label>
<span>Автозапуск BUC и LNB при включении</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.serviceSettings.autoStart" />
<span class="slider"></span>
</span>
</label>
</div>
</div>
<button class="action-button" @click="settingsSubmitBucLnb()">Сохранить <span class="submit-spinner" v-show="submitStatus.bucLnb"></span></button>
</div>
<div class="tabs-body-item" v-if="activeTab === 'qos' && settingFetchComplete">
<h2>Настройки QoS</h2>
<div class="settings-set-container">
<label>
<span>Активировать QoS</span>
<span class="toggle-input"><input type="checkbox" v-model="param.qos.en" /><span class="slider"></span></span>
</label>
</div>
<template>
<div v-for="classesGroup in ['rt1', 'rt2', 'rt3', 'cd']">
<h3>Классы {{ classesGroup.toUpperCase() }} <button class="action-button" @click="qosAddClass(classesGroup)"> + </button></h3>
<details v-for="(qosClass, index) in param.qos[classesGroup]" :key="index" class="settings-set-container">
<summary>
<span v-if="classesGroup === 'cd'">#{{ index }} CIR={{ qosClass.cir }}кбит, PIR={{ qosClass.pir }}кбит {{ qosClass.description }}</span>
<span v-if="classesGroup !== 'cd'">#{{ index }} CBR={{ qosClass.cir }}кбит {{ qosClass.description }}</span>
<span class="summary-actions">
<label>
<span class="toggle-input">
<input type="checkbox" v-model="qosClass.isEnabled" />
<span class="slider"></span>
</span>
</label>
</span>
</summary>
<label>
<span v-if="classesGroup === 'cd'">CIR</span> <span v-if="classesGroup !== 'cd'">CBR</span>
<input v-model="qosClass.cir" type="number"/>
</label>
<label v-if="classesGroup === 'cd'">
<span>PIR</span>
<input v-model="qosClass.pir" type="number"/>
</label>
<label>
<span>Описание</span>
<input v-model="qosClass.description"/>
</label>
<h3>Фильтры ({{ qosClass.filters.length }})</h3>
<div>
<button class="action-button" @click="qosClassAddRule(classesGroup, index)">Добавить правило</button>
</div>
<details v-for="(filter, filterIndex) in qosClass.filters" :key="filterIndex" class="settings-set-container">
<summary>
<span>#{{ filterIndex }} {{ qosGenerateRuleDescription(filter) }}</span>
<span class="summary-actions">
<label>
<span class="toggle-input">
<input type="checkbox" v-model="filter.isEnabled" />
<span class="slider"></span>
</span>
</label>
<button class="dangerous-button" @click="qosDelFilter(classesGroup, index, filterIndex)">Del</button>
</span>
</summary>
<label>
<span>VLAN ID</span>
<!-- singleVlanExpr: (([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4}))-->
<!-- expr: ^(((single,)+single)|single)$-->
<input v-model="filter.vlan" type="text" pattern="^((((([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})),)+(([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})))|(([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})))$">
</label>
<div>
<span>Протокол L3</span>
<label class="l3-proto-label"><span>AH:</span><input type="checkbox" value="ah" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>COMP:</span><input type="checkbox" value="comp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>DCCP:</span><input type="checkbox" value="dccp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>ESP:</span><input type="checkbox" value="esp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>ICMP:</span><input type="checkbox" value="icmp" v-model="filter.proto"></label>
<!-- <label class="l3-proto-label"><span>ICMPV6:</span><input type="checkbox" value="icmpv6" v-model="filter.proto"></label>-->
<label class="l3-proto-label"><span>SCTP:</span><input type="checkbox" value="sctp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>TCP:</span><input type="checkbox" value="tcp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>UDP:</span><input type="checkbox" value="udp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>UDPLITE:</span><input type="checkbox" value="udplite" v-model="filter.proto"></label>
</div>
<label>
<span>Порт источника</span>
<input v-model="filter.sport" type="text" pattern="^((((([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})),)+(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))|(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))$">
</label>
<label>
<span>Порт назначения</span>
<input v-model="filter.dport" type="text" pattern="^((((([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})),)+(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))|(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))$">
</label>
<label>
<span>IP источника</span>
<input v-model="filter.ip_src" type="text">
</label>
<label>
<span>IP назначения</span>
<input v-model="filter.ip_dest" type="text">
</label>
<label>
<span>Метка IP.DSCP</span>
<input v-model="filter.dscp" type="text">
</label>
</details>
<div>
<button class="dangerous-button" @click="qosDelClass(classesGroup, index)">Удалить класс QoS</button>
</div>
</details>
</div>
</template>
<button class="action-button" @click="settingsSubmitQoS()">Применить <span class="submit-spinner" v-show="submitStatus.qos"></span></button>
<h2 hidden>Настройки TCP-акселерации</h2>
<div hidden class="settings-set-container">
<label>
<span>Активировать акселерацию</span>
<span class="toggle-input"><input type="checkbox" v-model="param.tcpAccel.en" /><span class="slider"></span></span>
</label>
<label>
<span>Максимальное количество соединений</span>
<input type="number" v-model="param.tcpAccel.maxConnections" min="1" max="10000" />
</label>
</div>
<button hidden class="action-button" @click="settingsSubmitTcpAccel()">Применить <span class="submit-spinner" v-show="submitStatus.tcpAccel"></span></button>
</div>
<div class="tabs-body-item" v-if="activeTab === 'admin' && settingFetchComplete">
<h2>Настройки сети</h2>
<div class="settings-set-container">
<h3>Интерфейс управления</h3>
<label>
<span>IP адрес/маска</span>
<input v-model="param.network.managementIp" required type="text" pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$">
</label>
<label>
<span>Шлюз интерфейса управления</span>
<input v-model="param.network.managementGateway" type="text" pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$">
</label>
<label>
<span>Режим модема</span>
<select v-model="param.network.mode">
<option value="l2">Коммутатор</option>
<option value="l3">Маршрутизатор</option>
</select>
</label>
<h3>Интерфейс данных</h3>
<label v-if="param.network.mode === 'l3'">
<span>IP адрес/маска</span>
<input v-model="param.network.dataIp" required type="text" pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$">
</label>
<label>
<span>MTU</span>
<input v-model="param.network.dataMtu" required type="number" min="1500" max="9000">
</label>
<button class="action-button" @click="settingsSubmitNetwork()">Применить <span class="submit-spinner" v-show="submitStatus.network"></span></button>
</div>
<div class="settings-set-container">
<h3>Отладка</h3>
<label>
<span>Передача отладочной информации</span>
<span class="toggle-input">
<input type="checkbox" v-model="param.debugSend.en" />
<span class="slider"></span>
</span>
</label>
<label v-if="param.debugSend.en">
<span>IP адрес получателя</span>
<input v-model="param.debugSend.receiverIp" required type="text" pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}">
</label>
<label>
<span>Порт для данных</span>
<input v-model="param.debugSend.portCinC" type="number" min="0" max="65535">
</label>
<label>
<span>Порт для CinC</span>
<input v-model="param.debugSend.portData" type="number" min="0" max="65535">
</label>
<label>
<span>Таймаут</span>
<input v-model="param.debugSend.timeout" type="number" pattern="^[0-9]+$">
</label>
<button class="action-button" @click="settingsSubmitDebugSend()">Применить <span class="submit-spinner" v-show="submitStatus.network"></span></button>
</div>
<div class="settings-set-container">
<h3>Управление ПО</h3>
<table>
<tbody>
<tr><th>Версия ПО</th><td>{{ about.firmwareVersion }}</td></tr>
<tr><th>ID модема</th><td>{{ about.modemUid }}</td></tr>
<tr><th>Серийный номер</th><td>{{ about.modemSn }}</td></tr>
<tr><th>MAC интерфейса менеджмента</th><td>{{ about.macManagement }}</td></tr>
<tr><th>MAC интерфейса управления</th><td>{{ about.macData }}</td></tr>
</tbody>
</table>
<div>
<button class="dangerous-button" @click="doModemReboot()">Перезагрузить модем <span class="submit-spinner" v-show="submitStatus.modemReboot !== null"></span></button>
</div>
<div hidden> <!-- v-show="submitStatus.modemReboot !== null" -->
<img src="/internet.jpg" loading="lazy" alt="internet"/>
</div>
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<h3>Обновление ПО</h3>
<label>
<span>Файл {{ this.uploadFw.progress !== null ? `(${this.uploadFw.progress}%)` : '' }}</span>
<input type="file" accept="application/zip" @change="(e) => { this.uploadFw.filename = e.target.files[0] }">
<span v-if="uploadFw.sha256 !== null">SHA256: {{ uploadFw.sha256 }}</span>
</label>
<button class="action-button" @click="settingsUploadUpdate()">Загрузить<span class="submit-spinner" v-show="submitStatus.firmwareUpload"></span></button>
<button class="dangerous-button" v-show="uploadFw.sha256 !== null" @click="settingsPerformFirmwareUpgrade()">Обновить встроенное ПО <span class="submit-spinner" v-show="submitStatus.firmwareUpgrade"></span></button>
</div>
</div>
<p>Последнее обновление статистики: {{ lastUpdateTime }}</p>
</div>
</div>
<script src="/js/vue.js"></script>
<script>
function updateHeaderHeight() {
const header = document.querySelector('header');
document.body.style.setProperty('--header-height', `${header.offsetHeight}px`);
}
window.addEventListener('load', updateHeaderHeight);
window.addEventListener('resize', updateHeaderHeight);
// const router = useRouter();
const availableTabs = ['monitoring', 'setup', 'qos', 'admin']
const defaultTab = availableTabs[0]
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return defaultTab
}
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
// NOTE модкоды со скоростью хода 3/5 не работают
const modcods = [
"DUMMY",
"QPSK 1/4",
"QPSK 1/3",
"QPSK 2/5",
"QPSK 1/2",
"QPSK 3/5", // отключено
"QPSK 2/3",
"QPSK 3/4",
"QPSK 4/5",
"QPSK 5/6",
"QPSK 8/9",
"QPSK 9/10",
"8PSK 3/5", // отключено
"8PSK 2/3",
"8PSK 3/4",
"8PSK 5/6",
"8PSK 8/9",
"8PSK 9/10",
"16APSK 2/3",
"16APSK 3/4",
"16APSK 4/5",
"16APSK 5/6",
"16APSK 8/9",
"16APSK 9/10",
"32APSK 3/4",
"32APSK 4/5",
"32APSK 5/6",
"32APSK 8/9",
"32APSK 9/10",
]
if (typeof modcod != "number" || modcod < 0 || modcod >= modcod.length) {
return "?";
}
return modcods[modcod]
}
function toModcod(modulation, speed) {
switch (modulation.toLowerCase()) {
case 'qpsk':
switch (speed) {
case '1/4': return 1
case '1/3': return 2
case '2/5': return 3
case '1/2': return 4
case '3/5': return 5 // отключено
case '2/3': return 6
case '3/4': return 7
case '4/5': return 8
case '5/6': return 9
case '8/9': return 10
case '9/10': return 11
default: return 1 // минимальная скорость
}
case '8psk':
switch (speed) {
case '3/5': return 12 // отключено
case '2/3': return 13
case '3/4': return 14
case '5/6': return 15
case '8/9': return 16
case '9/10': return 17
default: return 13 // минимальная скорость
}
case '16apsk':
switch (speed) {
case '2/3': return 18
case '3/4': return 19
case '4/5': return 20
case '5/6': return 21
case '8/9': return 22
case '9/10': return 23
default: return 18 // минимальная скорость
}
case '32apsk':
switch (speed) {
case '3/4': return 24
case '4/5': return 25
case '5/6': return 26
case '8/9': return 27
case '9/10': return 28
default: return 24
}
}
}
function extractModulationAndSpeedFromModcod(modcod) {
switch (modcod) {
case 1: return { modulation: 'qpsk', speed: '1/4' }
case 2: return { modulation: 'qpsk', speed: '1/3' }
case 3: return { modulation: 'qpsk', speed: '2/5' }
case 4: return { modulation: 'qpsk', speed: '1/2' }
case 5: return { modulation: 'qpsk', speed: '3/5' }
case 6: return { modulation: 'qpsk', speed: '2/3' }
case 7: return { modulation: 'qpsk', speed: '3/4' }
case 8: return { modulation: 'qpsk', speed: '4/5' }
case 9: return { modulation: 'qpsk', speed: '5/6' }
case 10: return { modulation: 'qpsk', speed: '8/9' }
case 11: return { modulation: 'qpsk', speed: '9/10' }
case 12: return { modulation: '8psk', speed: '3/5' }
case 13: return { modulation: '8psk', speed: '2/3' }
case 14: return { modulation: '8psk', speed: '3/4' }
case 15: return { modulation: '8psk', speed: '5/6' }
case 16: return { modulation: '8psk', speed: '8/9' }
case 17: return { modulation: '8psk', speed: '9/10' }
case 18: return { modulation: '16apsk', speed: '2/3' }
case 19: return { modulation: '16apsk', speed: '3/4' }
case 20: return { modulation: '16apsk', speed: '4/5' }
case 21: return { modulation: '16apsk', speed: '5/6' }
case 22: return { modulation: '16apsk', speed: '8/9' }
case 23: return { modulation: '16apsk', speed: '9/10' }
case 24: return { modulation: '32apsk', speed: '3/4' }
case 25: return { modulation: '32apsk', speed: '4/5' }
case 26: return { modulation: '32apsk', speed: '5/6' }
case 27: return { modulation: '32apsk', speed: '8/9' }
case 28: return { modulation: '32apsk', speed: '9/10' }
}
return { modulation: 'qpsk', speed: '1/4' }
}
const app = new Vue({
el: '#app',
data: {
isCinC: false,
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
rxTx: false,
cinc: false,
bucLnb: false,
qos: false,
network: false,
debugSend: false,
tcpAccel: false,
firmwareUpload: false,
firmwareUpgrade: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
stat_rx: {
// индикаторы
state: '?', // общее состояние
sym_sync_lock: '?', // захват символьной
freq_search_lock: '?', // Захват поиска по частоте
afc_lock: '?', // захват ФАПЧ
pkt_sync: '?', // захват пакетной синхронизации
// куча других параметров, идет в том же порядке, что и в таблице
snr: '?', rssi: '?',
modcod: '?', frameSizeNormal: '?',
isPilots: '?',
symError: '?',
freqErr: '?', freqErrAcc: '?',
inputSignalLevel: '?',
pllError: '?',
speedOnRxKbit: '?',
speedOnIifKbit: '?',
// статистика пакетов
packetsOk: '?', packetsBad: '?', packetsDummy: '?',
},
stat_tx: {
// состояние
state: '?',
// прочие поля
snr: '?', modcod: '?', frameSizeNormal: '?', isPilots: '?', speedOnTxKbit: '?', speedOnIifKbit: '?',
},
stat_cinc: {
occ: '?',
correlator: null,
correlatorFails: '?',
freqErr: '?', freqErrAcc: '?',
channelDelay: '?'
},
stat_device: { // температурные датчики
adrv: 0, zynq: 0, fpga: 0
},
param: {
general: {
isCinC: Boolean,
txEn: Boolean, // включен/выключен
modulatorMode: 'normal', // режим работы модулятора
autoStartTx: Boolean, // было "режим работы передатчика"
isTestInputData: Boolean, // входные данные: eth или test
},
tx: {
attenuation: Number, // ослабление
rolloff: Number,
cymRate: Number,
centerFreq: Number,
},
dvbs2: {
mode: null, // ccm/acm
frameSizeNormal: null, // 'normal' / 'short'
// isPilots: false,
// CCM
ccm_modulation: null,
ccm_speed: null,
// ACM
acm_maxModulation: null,
acm_maxSpeed: null,
acm_minModulation: null,
acm_minSpeed: null,
snrReserve: null,
servicePacketPeriod: null,
},
// авто-регулировка мощности
acm: {
en: false,
maxAttenuation: null,
minAttenuation: null,
requiredSnr: null,
},
rx: {
gainMode: null, // 'auto'/'manual' режим управления усилением
manualGain: 0, // усиление, только для ручного режима
spectrumInversion: false,
rolloff: 0,
cymRate: 100000,
centerFreq: 1200000.0,
},
cinc: {
mode: null, // 'positional' | 'delay'
searchBandwidth: 0, // полоса поиска в кГц
position: {
station: {
latitude: 0,
longitude: 0
},
satelliteLongitude: 0,
},
delayMin: 0,
delayMax: 0
},
buc: {
refClk10M: false, // подача опоры 10MHz
powering: 0 // 0, 24, 48
},
lnb: {
refClk10M: false, // подача опоры 10MHz
powering: 0 // 0, 13, 18, 24
},
serviceSettings: {
refClk10M: false, // подача опоры 10MHz
autoStart: false
},
network: {
managementIp: '', // 0.0.0.0/24
managementGateway: '',
mode: String, // l2 | l3
dataIp: '', //
dataMtu: 1500
},
debugSend: {
en: false,
receiverIp: '0.0.0.0', // 0.0.0.0
portCinC: 0,
portData: 0,
timeout: 0
},
qos: {
en: false,
rt1: [],
rt2: [],
rt3: [],
cd: [],
},
tcpAccel: {
en: false,
maxConnections: 128
},
},
uploadFw: {
progress: null,
filename: null,
sha256: null
},
// эти "настройки" - read only
about: {
firmwareVersion: '?',
modemUid: '?',
modemSn: '?',
macManagement: '?',
macData: '?',
},
testState: false,
initState: '',
lastUpdateTime: new Date(),
activeTab: getCurrentTab(),
settingFetchComplete: false,
},
methods: {
getAvailableModcods(modulation) {
// NOTE модкоды со скоростью хода 3/5 не работают
switch (modulation) {
case 'qpsk':
return ['1/4', '1/3', '2/5', '1/2', /* '3/5',*/ '2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '8psk':
return [/* '3/5',*/ '2/3', '3/4', '5/6', '8/9', '9/10']
case '16apsk':
return ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '32apsk':
return ['3/4', '4/5', '5/6', '8/9', '9/10']
default:
return []
}
},
updateStatistics(vals) {
this.lastUpdateTime = new Date();
this.initState = vals["mainState"]["initState"]
this.isCinC = vals["mainState"]["isCinC"]
this.stat_rx.state = vals["mainState"]["rx.state"]
this.stat_rx.sym_sync_lock = vals["mainState"]["rx.sym_sync_lock"]
this.stat_rx.freq_search_lock = vals["mainState"]["rx.freq_search_lock"]
this.stat_rx.afc_lock = vals["mainState"]["rx.afc_lock"]
this.stat_rx.pkt_sync = vals["mainState"]["rx.pkt_sync"]
this.stat_rx.snr = vals["mainState"]["rx.snr"]
this.stat_rx.rssi = vals["mainState"]["rx.rssi"]
this.stat_rx.modcod = modcodToStr(vals["mainState"]["rx.modcod"])
this.stat_rx.frameSizeNormal = vals["mainState"]["rx.frameSizeNormal"]
this.stat_rx.isPilots = vals["mainState"]["rx.isPilots"]
this.stat_rx.symError = vals["mainState"]["rx.symError"]
this.stat_rx.freqErr = vals["mainState"]["rx.freqErr"]
this.stat_rx.freqErrAcc = vals["mainState"]["rx.freqErrAcc"]
this.stat_rx.inputSignalLevel = vals["mainState"]["rx.inputSignalLevel"]
this.stat_rx.pllError = vals["mainState"]["rx.pllError"]
this.stat_rx.speedOnRxKbit = vals["mainState"]["rx.speedOnRxKbit"]
this.stat_rx.speedOnIifKbit = vals["mainState"]["rx.speedOnIifKbit"]
this.stat_rx.packetsOk = vals["mainState"]["rx.packetsOk"]
this.stat_rx.packetsBad = vals["mainState"]["rx.packetsBad"]
this.stat_rx.packetsDummy = vals["mainState"]["rx.packetsDummy"]
this.stat_tx.state = vals["mainState"]["tx.state"]
this.stat_tx.snr = vals["mainState"]["tx.snr"]
this.stat_tx.modcod = modcodToStr(vals["mainState"]["tx.modcod"])
this.stat_tx.frameSizeNormal = vals["mainState"]["tx.frameSizeNormal"]
this.stat_tx.isPilots = vals["mainState"]["tx.isPilots"]
this.stat_tx.speedOnTxKbit = vals["mainState"]["tx.speedOnTxKbit"]
this.stat_tx.speedOnIifKbit = vals["mainState"]["tx.speedOnIifKbit"]
this.stat_cinc.occ = vals["mainState"]["cinc.occ"]
this.stat_cinc.correlator = vals["mainState"]["cinc.correlator"]
this.stat_cinc.correlatorFails = vals["mainState"]["cinc.correlatorFails"]
this.stat_cinc.freqErr = vals["mainState"]["cinc.freqErr"]
this.stat_cinc.freqErrAcc = vals["mainState"]["cinc.freqErrAcc"]
this.stat_cinc.channelDelay = vals["mainState"]["cinc.channelDelay"]
this.stat_device.adrv = vals["mainState"]["device.adrv"]
this.stat_device.zynq = vals["mainState"]["device.zynq"]
this.stat_device.fpga = vals["mainState"]["device.fpga"]
this.testState = vals["mainState"]["testState"]
},
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST'
}).then(() => {
this.stat_rx.packetsOk = 0
this.stat_rx.packetsBad = 0
this.stat_rx.packetsDummy = 0
})
},
settingsSubmitRxTx() {
if (this.submitStatus.rxTx) { return }
// потом добавить: "dvbs2.isPilots": this.param.dvbs2.isPilots
let query = {
"general.isCinC": this.param.general.isCinC,
"general.txEn": this.param.general.txEn,
"general.modulatorMode": this.param.general.modulatorMode,
"general.autoStartTx": this.param.general.autoStartTx,
"general.isTestInputData": this.param.general.isTestInputData,
"tx.attenuation": this.param.tx.attenuation,
"tx.rolloff": this.param.tx.rolloff,
"tx.cymRate": this.param.tx.cymRate,
"tx.centerFreq": this.param.tx.centerFreq,
"dvbs2.isAcm": this.param.dvbs2.mode === 'acm',
"dvbs2.frameSizeNormal": this.param.dvbs2.frameSizeNormal,
"dvbs2.ccm_modcod": toModcod(this.param.dvbs2.ccm_modulation, this.param.dvbs2.ccm_speed),
"dvbs2.acm_minModcod": toModcod(this.param.dvbs2.acm_minModulation, this.param.dvbs2.acm_minSpeed),
"dvbs2.acm_maxModcod": toModcod(this.param.dvbs2.acm_maxModulation, this.param.dvbs2.acm_maxSpeed),
"dvbs2.snrReserve": this.param.dvbs2.snrReserve,
"dvbs2.servicePacketPeriod": this.param.dvbs2.servicePacketPeriod,
"acm.en": this.param.acm.en,
"acm.maxAttenuation": this.param.acm.maxAttenuation,
"acm.minAttenuation": this.param.acm.minAttenuation,
"acm.requiredSnr": this.param.acm.requiredSnr,
"rx.gainMode": this.param.rx.gainMode,
"rx.manualGain": this.param.rx.manualGain,
"rx.spectrumInversion": this.param.rx.spectrumInversion,
"rx.rolloff": this.param.rx.rolloff,
"rx.cymRate": this.param.rx.cymRate,
"rx.centerFreq": this.param.rx.centerFreq
}
this.submitStatus.rxTx = true
fetch('/api/set/rxtx', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.rxTx = false
this.updateRxTxSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.rxTx = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
settingsSubmitCinC() {
if (this.submitStatus.cinc) { return }
let query = {
"cinc.mode": this.param.cinc.mode,
"cinc.searchBandwidth": this.param.cinc.searchBandwidth,
"cinc.position.station.latitude": this.param.cinc.position.station.latitude,
"cinc.position.station.longitude": this.param.cinc.position.station.longitude,
"cinc.position.satelliteLongitude": this.param.cinc.position.satelliteLongitude,
"cinc.delayMin": this.param.cinc.delayMin,
"cinc.delayMax": this.param.cinc.delayMax
}
this.submitStatus.cinc = true
fetch('/api/set/cinc', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.cinc = false
this.updateCincSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.cinc = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
settingsSubmitBucLnb() {
if (this.submitStatus.bucLnb) { return }
let query = {
"buc.refClk10M": this.param.buc.refClk10M,
"buc.powering": parseInt(this.param.buc.powering),
"lnb.refClk10M": this.param.lnb.refClk10M,
"lnb.powering": parseInt(this.param.lnb.powering),
"serviceSettings.refClk10M": this.param.serviceSettings.refClk10M,
"serviceSettings.autoStart": this.param.serviceSettings.autoStart
}
if (confirm('Вы уверены, что хотите сохранить настройки BUC и LNB?')) {
this.submitStatus.bucLnb = true
fetch('/api/set/bucLnb', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.bucLnb = false
this.updateBucLnbSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.bucLnb = false
alert(`Ошибка при применении настроек: ${reason}`)
})
}
},
settingsSubmitQoS() {
if (this.submitStatus.qos) { return }
this.submitStatus.qos = true
function _translateQosClass(trafficClass, qc) {
let res = {
cir: qc['cir'],
description: qc['description'],
filters: []
}
if (trafficClass === 'cd') {
res.pir = qc.pir
}
if (!qc.isEnabled) {
res.disabled = true
}
for (const fi in qc.filters) {
let filter = {}
if (qc['filters'][fi].vlan !== "") { filter['vlan'] = qc['filters'][fi].vlan }
if (qc['filters'][fi].proto.length > 0) {
let tmp = "";
for (let pid = 0; pid < qc['filters'][fi].proto.length; pid++) {
if (pid !== 0) { tmp += ',' }
tmp += qc['filters'][fi].proto[pid]
}
filter['proto'] = tmp
}
if (qc['filters'][fi].sport !== "") { filter['sport'] = qc['filters'][fi].sport }
if (qc['filters'][fi].dport !== "") { filter['dport'] = qc['filters'][fi].dport }
if (qc['filters'][fi].ip_src !== "") { filter['ip_src'] = qc['filters'][fi].ip_src }
if (qc['filters'][fi].ip_dest !== "") { filter['ip_dest'] = qc['filters'][fi].ip_dest }
if (qc['filters'][fi].dscp !== "") { filter['dscp'] = qc['filters'][fi].dscp }
if (Object.keys(filter).length === 0) { continue }
if (!qc.filters[fi].isEnabled) { filter['disabled'] = true }
res.filters.push(filter)
}
if (res.filters.length === 0) {
// автоматическое выключение класса, если правил нет
res.disabled = true
}
return res
}
let query = {
"en": this.param.qos.en,
"rt1": [],
"rt2": [],
"rt3": [],
"cd": []
}
for (let i = 0; i < this.param.qos.rt1.length; i++) { query.rt1.push(_translateQosClass('rt1', this.param.qos.rt1[i])) }
for (let i = 0; i < this.param.qos.rt2.length; i++) { query.rt2.push(_translateQosClass('rt2', this.param.qos.rt2[i])) }
for (let i = 0; i < this.param.qos.rt3.length; i++) { query.rt3.push(_translateQosClass('rt3', this.param.qos.rt3[i])) }
for (let i = 0; i < this.param.qos.cd.length; i++) { query.cd.push(_translateQosClass('cd', this.param.qos.cd[i])) }
console.log(query)
fetch('/api/set/qos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.qos = false
this.updateQosSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.qos = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
settingsSubmitTcpAccel() {
if (this.submitStatus.tcpAccel) { return }
this.submitStatus.tcpAccel = true
fetch('/api/set/tcpAccel', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"tcpAccel.en": this.param.tcpAccel.en,
"tcpAccel.maxConnections": this.param.tcpAccel.maxConnections
})
}).then(async (resp) => {
this.submitStatus.tcpAccel = false
this.updateNetworkSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.tcpAccel = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
settingsSubmitNetwork() {
if (this.submitStatus.network) { return }
let query = {
"network.managementIp": this.param.network.managementIp,
"network.managementGateway": this.param.network.managementGateway,
"network.mode": this.param.network.mode,
"network.dataIp": this.param.network.dataIp,
"network.dataMtu": this.param.network.dataMtu
}
if (confirm('Вы уверены, что хотите сохранить настройки сети? После этого модем может стать недоступным.')) {
this.submitStatus.network = true
fetch('/api/set/network', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.network = false
this.updateNetworkSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.network = false
alert(`Ошибка при применении настроек: ${reason}`)
})
}
},
settingsSubmitDebugSend() {
if (this.submitStatus.debugSend) { return }
let query = {
"debugSend.en": this.param.debugSend.en,
"debugSend.receiverIp": this.param.debugSend.receiverIp,
"debugSend.portCinC": this.param.debugSend.portCinC,
"debugSend.portData": this.param.debugSend.portData,
"debugSend.timeout": this.param.debugSend.timeout
}
this.submitStatus.debugSend = true
fetch('/api/set/debugSend', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
}).then(async (resp) => {
this.submitStatus.debugSend = false
this.updateNetworkSettings(await resp.json())
}).catch((reason) => {
this.submitStatus.debugSend = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
async settingsUploadUpdate() {
if (!this.uploadFw.filename) {
alert('Выберите файл для загрузки');
return;
}
async function readFileAsArrayBuffer(fileName) {
return new Promise((resolve, reject) => {
if (!fileName) { reject(`Файл не выбран`); return }
const reader = new FileReader();
reader.onload = (e) => { resolve(reader.result) }
reader.onerror = (e) => { reject(e) }
reader.readAsArrayBuffer(fileName)
})
}
try {
this.submitStatus.firmwareUpload = true
this.uploadFw.progress = 0
const blob = await readFileAsArrayBuffer(this.uploadFw.filename)
const xhr = new XMLHttpRequest();
await new Promise((resolve) => {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
this.uploadFw.progress = Math.round((event.loaded / event.total) * 1000) / 10;
}
});
xhr.addEventListener("loadend", () => {
this.uploadFw.progress = 100
const rep = JSON.parse(xhr.responseText);
this.uploadFw.sha256 = rep['sha256']
resolve(xhr.readyState === 4 && xhr.status === 200);
});
xhr.open("PUT", "/api/firmwareUpdate", true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(blob);
});
} catch (e) {
alert(`Ошибка загрузки файла: ${e}`);
}
this.submitStatus.firmwareUpload = false
},
async settingsPerformFirmwareUpgrade() {
if (this.submitStatus.firmwareUpgrade) { return }
this.submitStatus.firmwareUpgrade = true
try {
await fetch('/api/doFirmwareUpgrade', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgrade = false
},
performUpdateSettings(reloadParts) {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
let vals = await d.json()
if (reloadParts !== undefined) {
if (reloadParts.indexOf('rxtx')) { this.updateRxTxSettings(vals) }
if (reloadParts.indexOf('cinc')) { this.updateCincSettings(vals) }
if (reloadParts.indexOf('buclnb')) { this.updateBucLnbSettings(vals) }
if (reloadParts.indexOf('qos')) { this.updateQosSettings(vals) }
} else {
this.updateSettings(vals)
}
}
doFetchSettings().then(() => {})
},
updateRxTxSettings(vals) {
this.submitStatus.rxTx = false
this.param.general.isCinC = vals["settings"]["general.isCinC"]
this.param.general.txEn = vals["settings"]["general.txEn"]
this.param.general.modulatorMode = vals["settings"]["general.modulatorMode"]
this.param.general.autoStartTx = vals["settings"]["general.autoStartTx"]
this.param.general.isTestInputData = vals["settings"]["general.isTestInputData"]
this.param.tx.attenuation = vals["settings"]["tx.attenuation"]
this.param.tx.rolloff = vals["settings"]["tx.rolloff"]
this.param.tx.cymRate = vals["settings"]["tx.cymRate"]
this.param.tx.centerFreq = vals["settings"]["tx.centerFreq"]
this.param.dvbs2.mode = (vals["settings"]["dvbs2.isAcm"] ? 'acm' : 'ccm')
this.param.dvbs2.frameSizeNormal = vals["settings"]["dvbs2.frameSizeNormal"]
// this.param.dvbs2.isPilots = vals["settings"]["dvbs2.isPilots"]
let m = extractModulationAndSpeedFromModcod(vals["settings"]["dvbs2.ccm_modcod"])
this.param.dvbs2.ccm_modulation = m.modulation
this.param.dvbs2.ccm_speed = m.speed
m = extractModulationAndSpeedFromModcod(vals["settings"]["dvbs2.acm_maxModcod"])
this.param.dvbs2.acm_maxModulation = m.modulation
this.param.dvbs2.acm_maxSpeed = m.speed
m = extractModulationAndSpeedFromModcod(vals["settings"]["dvbs2.acm_minModcod"])
this.param.dvbs2.acm_minModulation = m.modulation
this.param.dvbs2.acm_minSpeed = m.speed
this.param.dvbs2.snrReserve = vals["settings"]["dvbs2.snrReserve"]
this.param.dvbs2.servicePacketPeriod = vals["settings"]["dvbs2.servicePacketPeriod"]
this.param.acm.en = vals["settings"]["acm.en"]
this.param.acm.maxAttenuation = vals["settings"]["acm.maxAttenuation"]
this.param.acm.minAttenuation = vals["settings"]["acm.minAttenuation"]
this.param.acm.requiredSnr = vals["settings"]["acm.requiredSnr"]
this.param.rx.gainMode = vals["settings"]["rx.gainMode"]
this.param.rx.manualGain = vals["settings"]["rx.manualGain"]
this.param.rx.spectrumInversion = vals["settings"]["rx.spectrumInversion"]
this.param.rx.rolloff = vals["settings"]["rx.rolloff"]
this.param.rx.cymRate = vals["settings"]["rx.cymRate"]
this.param.rx.centerFreq = vals["settings"]["rx.centerFreq"]
},
updateCincSettings(vals) {
this.submitStatus.cinc = false
this.param.cinc.mode = vals["settings"]["cinc.mode"]
this.param.cinc.searchBandwidth = vals["settings"]["cinc.searchBandwidth"]
this.param.cinc.position.station.latitude = vals["settings"]["cinc.position.station.latitude"]
this.param.cinc.position.station.longitude = vals["settings"]["cinc.position.station.longitude"]
this.param.cinc.position.satelliteLongitude = vals["settings"]["cinc.position.satelliteLongitude"]
this.param.cinc.delayMin = vals["settings"]["cinc.delayMin"]
this.param.cinc.delayMax = vals["settings"]["cinc.delayMax"]
},
updateBucLnbSettings(vals) {
this.submitStatus.bucLnb = false
this.param.buc.refClk10M = vals["settings"]["buc.refClk10M"]
this.param.buc.powering = vals["settings"]["buc.powering"]
this.param.lnb.refClk10M = vals["settings"]["lnb.refClk10M"]
this.param.lnb.powering = vals["settings"]["lnb.powering"]
this.param.serviceSettings.refClk10M = vals["settings"]["serviceSettings.refClk10M"]
this.param.serviceSettings.autoStart = vals["settings"]["serviceSettings.autoStart"]
},
updateQosSettings(vals) {
this.submitStatus.qos = false
this.param.qos.en = vals["settings"]["qos.enabled"]
const qosProfile = vals["settings"]["qos.profile"]
if (qosProfile !== null && qosProfile !== undefined) {
this.param.qos.rt1 = [] // .splice(0, this.param.qos.rt1.length)
this.param.qos.rt2 = [] // .splice(0, this.param.qos.rt2.length)
this.param.qos.rt3 = [] // .splice(0, this.param.qos.rt3.length)
this.param.qos.cd = [] // .splice(0, this.param.qos.cd.length)
for (let trafficClass in qosProfile) {
if (['rt1', 'rt2', 'rt3', 'cd'].indexOf(trafficClass) < 0) {
continue
}
if (Array.isArray(qosProfile[trafficClass])) {
for (let i = 0; i < qosProfile[trafficClass].length; i++) {
const qc = qosProfile[trafficClass][i]
let result = {
isEnabled: !qc.hasOwnProperty('disabled'),
cir: qc['cir'],
pir: 0,
description: qc['description'],
filters: []
}
if (trafficClass === 'cd') {
if (qc['pir']) {
result.pir = qc['pir']
}
}
for (let fi = 0; fi < qc['filters'].length; fi++) {
result.filters.push({
isEnabled: !qc['filters'][fi].hasOwnProperty('disabled'),
vlan: qc['filters'][fi].hasOwnProperty('vlan') ? qc['filters'][fi]['vlan'] : '',
proto: qc['filters'][fi].hasOwnProperty('proto') ? qc['filters'][fi]['proto'].split(',') : [],
sport: qc['filters'][fi].hasOwnProperty('sport') ? qc['filters'][fi]['sport'] : '',
dport: qc['filters'][fi].hasOwnProperty('dport') ? qc['filters'][fi]['dport'] : '',
ip_src: qc['filters'][fi].hasOwnProperty('ip_src') ? qc['filters'][fi]['ip_src'] : '',
ip_dest: qc['filters'][fi].hasOwnProperty('ip_dest') ? qc['filters'][fi]['ip_dest'] : '',
dscp: qc['filters'][fi].hasOwnProperty('dscp') ? qc['filters'][fi]['dscp'] : ''
})
}
switch (trafficClass) {
case 'rt1': this.param.qos.rt1.push(result); break
case 'rt2': this.param.qos.rt2.push(result); break
case 'rt3': this.param.qos.rt3.push(result); break
case 'cd': this.param.qos.cd.push(result); break
}
}
}
}
}
},
updateNetworkSettings(vals) {
this.submitStatus.network = false
this.param.network.managementIp = vals["settings"]["network.managementIp"]
this.param.network.managementGateway = vals["settings"]["network.managementGateway"]
this.param.network.mode = vals["settings"]["network.mode"]
this.param.network.dataIp = vals["settings"]["network.dataIp"]
this.param.network.dataMtu = vals["settings"]["network.dataMtu"]
},
updateDebugSendSettings(vals) {
this.submitStatus.debugSend = false
this.param.debugSend.en = vals["settings"]["debugSend.en"]
this.param.debugSend.receiverIp = vals["settings"]["debugSend.receiverIp"]
this.param.debugSend.portCinC = vals["settings"]["debugSend.portCinC"]
this.param.debugSend.portData = vals["settings"]["debugSend.portData"]
this.param.debugSend.timeout = vals["settings"]["debugSend.timeout"]
},
updateSettings(vals) {
this.settingFetchComplete = true
this.updateRxTxSettings(vals)
this.updateCincSettings(vals)
this.updateBucLnbSettings(vals)
this.updateQosSettings(vals)
this.updateNetworkSettings(vals)
this.updateDebugSendSettings(vals)
},
qosAddClass(name) {
let res = {
isEnabled: true,
cir: 0,
pir: 0,
description: "",
filters: []
}
switch (name) {
case 'rt1': this.param.qos.rt1.push(res); break
case 'rt2': this.param.qos.rt2.push(res); break
case 'rt3': this.param.qos.rt3.push(res); break
case 'cd': this.param.qos.cd.push(res); break
}
},
qosClassAddRule(name, index) {
let rule = {
isEnabled: true,
vlan: "",
proto: [],
sport: "",
dport: "",
ip_src: "",
ip_dest: "",
dscp: ""
}
switch (name) {
case 'rt1': this.param.qos.rt1[index].filters.push(rule); break
case 'rt2': this.param.qos.rt2[index].filters.push(rule); break
case 'rt3': this.param.qos.rt3[index].filters.push(rule); break
case 'cd': this.param.qos.cd[index].filters.push(rule); break
}
},
qosDelClass(name, index) {
switch (name) {
case 'rt1': this.param.qos.rt1.splice(index, 1); break
case 'rt2': this.param.qos.rt2.splice(index, 1); break
case 'rt3': this.param.qos.rt3.splice(index, 1); break
case 'cd': this.param.qos.cd.splice(index, 1); break
}
},
qosDelFilter(name, index, filterIndex) {
switch (name) {
case 'rt1': this.param.qos.rt1[index].filters.splice(filterIndex, 1); break
case 'rt2': this.param.qos.rt2[index].filters.splice(filterIndex, 1); break
case 'rt3': this.param.qos.rt3[index].filters.splice(filterIndex, 1); break
case 'cd': this.param.qos.cd[index].filters.splice(filterIndex, 1); break
}
},
qosGenerateRuleDescription(filter) {
// попытка 1: просто отобразить все фильтры
let result = ""
let isFirst = true;
for (const key in filter) {
if (key === "isEnabled" || !filter[key] || (key === "proto" && filter['proto'].length === 0)) {
continue
}
if (isFirst) {
isFirst = false;
} else {
result += '; '
}
result += `${key}: ${filter[key]}`
}
if (result === "") {
return "пустой"
}
const maxResultLen = 60
if (result.length > maxResultLen) {
// попытка 2, отобразить что вообще в этом фильтре использовалось
result = ""
isFirst = true;
for (const key in filter) {
if (key === "isEnabled" || !filter[key] || (key === "proto" && filter['proto'].length === 0)) {
continue
}
if (isFirst) {
isFirst = false;
} else {
result += ', '
}
result += `${key}`
}
}
return result
},
correctModcodSpeed(modulation, speed) {
const mod = modulation.toLowerCase()
const available = {
"qpsk": ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"8psk": ['2/3', '3/4', '5/6', '8/9', '9/10'],
"16apsk": ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"32apsk": ['3/4', '4/5', '5/6', '8/9', '9/10']
}
if (mod in available) {
if (available[mod].indexOf(speed) >= 0) {
return speed
}
return available[mod][0]
}
return ""
},
doModemReboot() {
if (this.submitStatus.modemReboot !== null) {
return
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
}
},
mounted() {
const doFetchStatistics = async () => {
if (this.submitStatus.modemReboot !== null) {
this.initState = `Перезагрузка модема... Осталось ${this.submitStatus.modemReboot} сек`
this.submitStatus.modemReboot--
if (this.submitStatus.modemReboot <= 0) {
window.location.reload()
}
} else {
try {
let d = await fetch("/api/get/statistics")
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"
}
}
setTimeout(() => {
doFetchStatistics()
}, 1000)
}
const doFetchAbout = async () => {
try {
const fr = await fetch("/api/get/aboutFirmware")
const d = await fr.json()
this.about.firmwareVersion = d["fw.version"]
this.about.modemUid = d["fw.modemId"]
this.about.modemSn = d["fw.modemSn"]
this.about.macManagement = d["fw.macMang"]
this.about.macData = d["fw.macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
})
</script>
</body>
</html>