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

954 lines
54 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>ШПС Модем</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: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Передача: <span :class="{ indicator_good: statTx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Тест: <span :class="{ indicator_good: testState, indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
<span :class="{ value_bad: initState !== 'Успешная инициализация системы' }">{{ initState }}</span>
<div class="tabs-header">
<span style="font-weight:bold">ШПС Модем</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="#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 statistics-container">
<h2>Статистика приема</h2>
<table>
<tbody>
<tr><th>Прием</th><td><span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></td></tr>
<tr><th>Захват символьной</th><td><span :class="{ indicator_bad: statRx.sym_sync_lock === false, indicator_good: statRx.sym_sync_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват ФАПЧ</th><td><span :class="{ indicator_bad: statRx.afc_lock === false, indicator_good: statRx.afc_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват поиска по частоте</th><td><span :class="{ indicator_bad: statRx.freq_search_lock === false, indicator_good: statRx.freq_search_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват пакетной синхр.</th><td><span :class="{ indicator_bad: statRx.pkt_sync === false, indicator_good: statRx.pkt_sync === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ statRx.snr }} / {{ statRx.rssi }}</td></tr>
<tr><th>Modcod</th><td>{{ statRx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ statRx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ statRx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Символьная ошибка</th><td>{{ statRx.symError }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ statRx.freqErr }} / {{ statRx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ statRx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ statRx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ statRx.speedOnRxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statRx.speedOnIifKbit }} кбит/с</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статистика пакетов</td></tr>
<tr><th>Качественных пакетов</th><td>{{ statRx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ statRx.packetsBad }}</td></tr>
<tr><th>DUMMY</th><td>{{ statRx.packetsDummy }}</td></tr>
</tbody>
</table>
<button class="action-button" @click="resetPacketsStatistics()"> Сброс статистики </button>
</div>
<div class="settings-set-container statistics-container">
<h2>Статистика передачи</h2>
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>Modcod</th><td>{{ statTx.modcod }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
<tr><th>Центральная частота</th><td>{{ statTx.centerFreq }} кГц</td></tr>
<tr><th>Символьная скорость</th><td>{{ statTx.symSpeed }} ksymb</td></tr>
</tbody>
</table>
</div>
<div class="settings-set-container statistics-container">
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ statDevice.adrv }} °C</td></tr>
<tr><th>Температура ZYNQ</th><td>{{ statDevice.zynq }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ statDevice.fpga }} °C</td></tr>
<tr><th>Время работы устройства</th><td>{{ statOs.uptime }}</td></tr>
<tr><th>Средняя загрузка ЦП (1/5/15 мин.)</th><td>{{ statOs.load1 }}% {{ statOs.load5 }}% {{ statOs.load15 }}%</td></tr>
<tr><th>ОЗУ всего/свободно</th><td>{{ statOs.totalram }}МБ/{{ statOs.freeram }}МБ</td></tr>
</tbody>
</table>
</div>
</div>
<div class="tabs-body-item" v-if="activeTab === 'setup' && settingFetchComplete">
<h2>Настройки приема/передачи</h2>
<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="paramRxtx.txEn" /><span class="slider"></span></span>
</label>
<label>
<span>Автоматический запуск передатчика</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.txAutoStart" /><span class="slider"></span></span>
</label>
<label>
<span>Режим работы модулятора</span>
<select v-model="paramRxtx.txModulatorIsTest">
<option :value="false">Нормальный</option>
<option :value="true">Тест (CW)</option>
</select>
</label>
<label>
<span>Входные данные</span>
<select v-model="paramRxtx.txIsTestInput">
<option :value="false">Ethernet</option>
<option :value="true">Тест</option>
</select>
</label>
<h3>Параметры передачи</h3>
<label><span>Центральная частота, КГц</span><input type="number" v-model="paramRxtx.txCentralFreq" min="100000" max="6000000" step="0.01"/></label>
<label><span>Символьная скорость, Бод</span><input type="number" v-model="paramRxtx.txBaudrate" min="200000" max="54000000"/></label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.txRolloff">
<option :value="20">0.02</option>
<option :value="50">0.05</option>
<option :value="100">0.10</option>
<option :value="150">0.15</option>
<option :value="200">0.20</option>
<option :value="250">0.25</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.txSpreadCoef" min="-1000" max="1000" step="0.01"/></label>
<label><span>Ослабление, дБ</span><input type="number" v-model="paramRxtx.txAttenuation" min="-40" step="0.25"/></label>
</div>
<div class="settings-set-container">
<h3>Авто-регулировка мощности</h3>
<label>
<span>Авто-регулировка мощности</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.aupcEn" /><span class="slider"></span></span>
</label>
<label><span>Минимальное ослабление, дБ</span><input type="number" v-model="paramRxtx.aupcMinAttenuation" max="10" step="0.1"/></label>
<label><span>Максимальное ослабление, дБ</span><input type="number" v-model="paramRxtx.aupcMaxAttenuation" max="10" step="0.1"/></label>
<label><span>Требуемое ОСШ</span><input type="number" v-model="paramRxtx.aupcRequiredSnr" max="10" step="0.01"/></label>
</div>
<div class="settings-set-container">
<h3>Настройки приемника</h3>
<label>
<span>Режим управления усилением</span>
<select v-model="paramRxtx.rxAgcEn">
<option :value="false">РРУ</option>
<option :value="true">АРУ</option>
</select>
</label>
<label v-show="paramRxtx.rxAgcEn === false"><span>Усиление, дБ</span><input type="number" v-model="paramRxtx.rxManualGain" min="-40" max="40" step="0.01"/></label>
<label v-show="paramRxtx.rxAgcEn === true">
<span>Текущее усиление</span><span>{{ paramRxtx.rxManualGain }}</span>
</label>
<label>
<span>Инверсия спектра</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.rxSpectrumInversion" /><span class="slider"></span></span>
</label>
<label><span>Центральная частота, КГц</span><input type="number" v-model="paramRxtx.rxCentralFreq" min="100000" max="6000000" step="0.01"/></label>
<label><span>Символьная скорость, Бод</span><input type="number" v-model="paramRxtx.rxBaudrate" min="200000" max="54000000"/></label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.rxRolloff">
<option :value="20">0.02</option>
<option :value="50">0.05</option>
<option :value="100">0.10</option>
<option :value="150">0.15</option>
<option :value="200">0.20</option>
<option :value="250">0.25</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.rxSpreadCoef" min="-1000" max="1000" step="0.01"/></label>
</div>
</div>
<button class="action-button" @click="settingsSubmitRxtx()">Сохранить <span class="submit-spinner" v-show="submitStatus.rxtx"></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="paramBuclnb.bucRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание BUC</span>
<select v-model="paramBuclnb.bucPowering">
<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="paramBuclnb.lnbRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание LNB</span>
<select v-model="paramBuclnb.lnbPowering">
<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="paramBuclnb.srvRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Автозапуск BUC и LNB при включении</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.bucLnbAutoStart" /><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 === 'admin' && settingFetchComplete">
<h2>Настройки сети</h2>
<div class="settings-set-container">
<h3>Настройки интерфейса управления</h3>
<label>
<span>Интерфейс управления (a.d.d.r/mask)</span>
<input v-model="paramNetwork.managementIp" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$">
</label>
<label>
<span>Режим сети</span>
<select v-model="paramNetwork.isL2">
<option :value="false">Маршрутизатор</option>
<option :value="true">Коммутатор</option>
</select>
</label>
<label v-show="paramNetwork.isL2 === false">
<span>Интерфейс данных (/24)</span>
<input v-model="paramNetwork.dataIp" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</label>
<label><span>MTU интерфейса данных</span><input type="number" v-model="paramNetwork.dataMtu" min="1500" max="2000" step="1"/></label>
<label>
<span>Имя веб-сервера</span>
<input v-model="paramNetwork.serverName" type="text">
</label>
</div>
<button class="action-button" @click="settingsSubmitNetwork()">Сохранить <span class="submit-spinner" v-show="submitStatus.network"></span></button>
<h2>Система</h2>
<div class="settings-set-container statistics-container">
<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>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>
<div class="settings-set-container statistics-container">
<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?v=3.5.13"></script>
<script>
const availableTabs = ['monitoring', 'setup', 'admin']
// для обновления высоты хидера
function updateHeaderHeight() { const header = document.querySelector('header'); document.body.style.setProperty('--header-height', `${header.offsetHeight}px`); }
window.addEventListener('load', updateHeaderHeight); window.addEventListener('resize', updateHeaderHeight);
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return availableTabs[0]
}
function toLocaleStringWithSpaces(num) {
if (typeof num !== 'number') {
if (typeof num === 'string') { return num }
return String(num);
}
const numberString = num.toString()
const [integerPart, fractionalPart] = numberString.split('.')
const spacedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
if (fractionalPart) { return `${spacedIntegerPart}.${fractionalPart}` }
else { return spacedIntegerPart }
}
const app = Vue.createApp({
data() {
return {
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
rxtx: false,
buclnb: false,
network: false,
firmwareUpload: false,
firmwareUpgrade: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
// ========== include from 'common/all-params-data.js.j2'
paramRxtx: {
txEn: false,
txAutoStart: false,
txModulatorIsTest: false,
txIsTestInput: false,
txCentralFreq: 100000,
txBaudrate: 200000,
txRolloff: 20,
txSpreadCoef: -1000,
txAttenuation: -40,
aupcEn: false,
aupcMinAttenuation: 0,
aupcMaxAttenuation: 0,
aupcRequiredSnr: 0,
rxAgcEn: false,
rxManualGain: -40,
rxSpectrumInversion: false,
rxCentralFreq: 100000,
rxBaudrate: 200000,
rxRolloff: 20,
rxSpreadCoef: -1000,
},
paramBuclnb: {
bucRefClk10M: false,
bucPowering: 0,
lnbRefClk10M: false,
lnbPowering: 0,
srvRefClk10M: false,
bucLnbAutoStart: false,
},
paramNetwork: {
managementIp: null,
isL2: false,
dataIp: null,
dataMtu: 1500,
serverName: null,
},
// ========== include end from 'common/all-params-data.js.j2'
// ========== include from 'common/monitoring-data.js.j2'
statRx: {
// индикаторы
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: '?',
},
statTx: {
// состояние
state: '?',
// прочие поля
modcod: '?', speedOnTxKbit: '?', speedOnIifKbit: '?', centerFreq: '?', symSpeed: '?'
},
statDevice: { // температурные датчики
adrv: 0, zynq: 0, fpga: 0
},
statOs: {uptime: '?', load1: '?', load5: '?', load15: '?', totalram: '?', freeram: '?'},
// ========== include end from 'common/monitoring-data.js.j2'
// ========== include from 'common/setup-data.js.j2'
// ========== include end from 'common/setup-data.js.j2'
// ========== include from 'common/admin-data.js.j2'
// ========== include end from 'common/admin-data.js.j2'
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: {
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 ""
},
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 []
}
},
inputFormatNumber(src, validation) {
if (validation === null || validation === undefined) { validation = {} }
const rawVal = src.toString().replace(/[^0-9.,]/g, '').replace(',', '.')
let result = rawVal === '' ? 0 : parseFloat(rawVal)
const step = 'step' in validation ? validation['step']: 1.0
result = Math.round(result / step) * step
if ('min' in validation) { if (result <= validation['min']) { result = validation['min'] } }
if ('max' in validation) { if (result >= validation['max']) { result = validation['max'] } }
return toLocaleStringWithSpaces(result)
},
// ========== include from 'common/all-params-methods.js.j2'
settingsSubmitRxtx() {
if (this.submitStatus.rxtx) { return }
let query = {
"txEn": this.paramRxtx.txEn,
"txAutoStart": this.paramRxtx.txAutoStart,
"txModulatorIsTest": this.paramRxtx.txModulatorIsTest,
"txIsTestInput": this.paramRxtx.txIsTestInput,
"txCentralFreq": this.paramRxtx.txCentralFreq,
"txBaudrate": this.paramRxtx.txBaudrate,
"txRolloff": this.paramRxtx.txRolloff,
"txSpreadCoef": this.paramRxtx.txSpreadCoef,
"txAttenuation": this.paramRxtx.txAttenuation,
"aupcEn": this.paramRxtx.aupcEn,
"aupcMinAttenuation": this.paramRxtx.aupcMinAttenuation,
"aupcMaxAttenuation": this.paramRxtx.aupcMaxAttenuation,
"aupcRequiredSnr": this.paramRxtx.aupcRequiredSnr,
"rxAgcEn": this.paramRxtx.rxAgcEn,
"rxManualGain": this.paramRxtx.rxManualGain,
"rxSpectrumInversion": this.paramRxtx.rxSpectrumInversion,
"rxCentralFreq": this.paramRxtx.rxCentralFreq,
"rxBaudrate": this.paramRxtx.rxBaudrate,
"rxRolloff": this.paramRxtx.rxRolloff,
"rxSpreadCoef": this.paramRxtx.rxSpreadCoef,
}
this.submitStatus.rxtx = true
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateRxtxSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.rxtx = false })
},
settingsSubmitBuclnb() {
if (this.submitStatus.buclnb) { return }
{ if (!confirm("Применение неправильных настроек может вывести из строя оборудование! Продолжить?")) return }
let query = {
"bucRefClk10M": this.paramBuclnb.bucRefClk10M,
"bucPowering": this.paramBuclnb.bucPowering,
"lnbRefClk10M": this.paramBuclnb.lnbRefClk10M,
"lnbPowering": this.paramBuclnb.lnbPowering,
"srvRefClk10M": this.paramBuclnb.srvRefClk10M,
"bucLnbAutoStart": this.paramBuclnb.bucLnbAutoStart,
}
this.submitStatus.buclnb = true
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateBuclnbSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.buclnb = false })
},
settingsSubmitNetwork() {
if (this.submitStatus.network) { return }
{ if (!confirm("Применение этих настроек может сделать модем недоступным! Продолжить?")) return }
let query = {
"managementIp": this.paramNetwork.managementIp,
"isL2": this.paramNetwork.isL2,
"dataIp": this.paramNetwork.dataIp,
"dataMtu": this.paramNetwork.dataMtu,
"serverName": this.paramNetwork.serverName,
}
this.submitStatus.network = true
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateNetworkSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.network = false })
},
updateRxtxSettings(vals) {
this.submitStatus.rxtx = false
this.paramRxtx.txEn = vals["settings"]["rxtx"]["txEn"]
this.paramRxtx.txAutoStart = vals["settings"]["rxtx"]["txAutoStart"]
this.paramRxtx.txModulatorIsTest = vals["settings"]["rxtx"]["txModulatorIsTest"]
this.paramRxtx.txIsTestInput = vals["settings"]["rxtx"]["txIsTestInput"]
this.paramRxtx.txCentralFreq = vals["settings"]["rxtx"]["txCentralFreq"]
this.paramRxtx.txBaudrate = vals["settings"]["rxtx"]["txBaudrate"]
this.paramRxtx.txRolloff = vals["settings"]["rxtx"]["txRolloff"]
this.paramRxtx.txSpreadCoef = vals["settings"]["rxtx"]["txSpreadCoef"]
this.paramRxtx.txAttenuation = vals["settings"]["rxtx"]["txAttenuation"]
this.paramRxtx.aupcEn = vals["settings"]["rxtx"]["aupcEn"]
this.paramRxtx.aupcMinAttenuation = vals["settings"]["rxtx"]["aupcMinAttenuation"]
this.paramRxtx.aupcMaxAttenuation = vals["settings"]["rxtx"]["aupcMaxAttenuation"]
this.paramRxtx.aupcRequiredSnr = vals["settings"]["rxtx"]["aupcRequiredSnr"]
this.paramRxtx.rxAgcEn = vals["settings"]["rxtx"]["rxAgcEn"]
this.paramRxtx.rxManualGain = vals["settings"]["rxtx"]["rxManualGain"]
this.paramRxtx.rxSpectrumInversion = vals["settings"]["rxtx"]["rxSpectrumInversion"]
this.paramRxtx.rxCentralFreq = vals["settings"]["rxtx"]["rxCentralFreq"]
this.paramRxtx.rxBaudrate = vals["settings"]["rxtx"]["rxBaudrate"]
this.paramRxtx.rxRolloff = vals["settings"]["rxtx"]["rxRolloff"]
this.paramRxtx.rxSpreadCoef = vals["settings"]["rxtx"]["rxSpreadCoef"]
},
updateBuclnbSettings(vals) {
this.submitStatus.buclnb = false
this.paramBuclnb.bucRefClk10M = vals["settings"]["buclnb"]["bucRefClk10M"]
this.paramBuclnb.bucPowering = vals["settings"]["buclnb"]["bucPowering"]
this.paramBuclnb.lnbRefClk10M = vals["settings"]["buclnb"]["lnbRefClk10M"]
this.paramBuclnb.lnbPowering = vals["settings"]["buclnb"]["lnbPowering"]
this.paramBuclnb.srvRefClk10M = vals["settings"]["buclnb"]["srvRefClk10M"]
this.paramBuclnb.bucLnbAutoStart = vals["settings"]["buclnb"]["bucLnbAutoStart"]
},
updateNetworkSettings(vals) {
this.submitStatus.network = false
this.paramNetwork.managementIp = vals["settings"]["network"]["managementIp"]
this.paramNetwork.isL2 = vals["settings"]["network"]["isL2"]
this.paramNetwork.dataIp = vals["settings"]["network"]["dataIp"]
this.paramNetwork.dataMtu = vals["settings"]["network"]["dataMtu"]
this.paramNetwork.serverName = vals["settings"]["network"]["serverName"]
},
// ========== include end from 'common/all-params-methods.js.j2'
// ========== include from 'common/monitoring-methods.js.j2'
updateStatistics(vals) {
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
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") {
return "?";
}
if (modcod < 0 || modcod >= modcods.length) {
return `? (${modcod})`
}
return modcods[modcod]
}
this.lastUpdateTime = new Date();
this.initState = vals["state"]["initState"]
this.testState = vals["state"]["testState"]
this.statRx.state = vals["state"]["rx"]["state"]
this.statRx.sym_sync_lock = vals["state"]["rx"]["sym_sync_lock"]
this.statRx.freq_search_lock = vals["state"]["rx"]["freq_search_lock"]
this.statRx.afc_lock = vals["state"]["rx"]["afc_lock"]
this.statRx.pkt_sync = vals["state"]["rx"]["pkt_sync"]
this.statRx.snr = Math.round(vals["state"]["rx"]["snr"] * 10) / 10
this.statRx.rssi = Math.round(vals["state"]["rx"]["rssi"] * 10) / 10
this.statRx.modcod = modcodToStr(vals["state"]["rx"]["modcod"])
this.statRx.frameSizeNormal = vals["state"]["rx"]["frameSizeNormal"]
this.statRx.isPilots = vals["state"]["rx"]["isPilots"]
this.statRx.symError = vals["state"]["rx"]["symError"]
this.statRx.freqErr = vals["state"]["rx"]["freqErr"]
this.statRx.freqErrAcc = vals["state"]["rx"]["freqErrAcc"]
this.statRx.inputSignalLevel = vals["state"]["rx"]["inputSignalLevel"]
this.statRx.pllError = vals["state"]["rx"]["pllError"]
this.statRx.speedOnRxKbit = Math.round(vals["state"]["rx"]["speedOnRxKbit"] * 100) / 100
this.statRx.speedOnIifKbit = Math.round(vals["state"]["rx"]["speedOnIifKbit"] * 100) / 100
this.statRx.packetsOk = vals["state"]["rx"]["packetsOk"]
this.statRx.packetsBad = vals["state"]["rx"]["packetsBad"]
this.statRx.packetsDummy = vals["state"]["rx"]["packetsDummy"]
this.statTx.state = vals["state"]["tx"]["state"]
this.statTx.modcod = modcodToStr(vals["state"]["tx"]["modcod"])
this.statTx.speedOnTxKbit = Math.round(vals["state"]["tx"]["speedOnTxKbit"] * 100) / 100
this.statTx.speedOnIifKbit = Math.round(vals["state"]["tx"]["speedOnIifKbit"] * 100) / 100
this.statTx.centerFreq = vals["state"]["tx"]["centerFreq"]
this.statTx.symSpeed = vals["state"]["tx"]["symSpeed"]
this.statDevice.adrv = vals["state"]["device"]["adrv"]
this.statDevice.zynq = vals["state"]["device"]["zynq"]
this.statDevice.fpga = vals["state"]["device"]["fpga"]
// аптайм приходит в секундах, надо преобразовать его в человеко-читаемый вид
let uptime = vals["state"]["device"]["uptime"]
if (uptime) {
let secs = uptime % 60; uptime = Math.floor(uptime / 60)
let mins = uptime % 60; uptime = Math.floor(uptime / 60)
let hours = uptime % 24
uptime = Math.floor( uptime / 24)
let res = `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
if (uptime > 0) { res = `${uptime} дней, ` + res }
this.statOs.uptime = res
} else {
this.statOs.uptime = '?'
}
this.statOs.load1 = Math.round(vals["state"]["device"]["load1min"] * 100) / 100
this.statOs.load5 = Math.round(vals["state"]["device"]["load5min"] * 100) / 100
this.statOs.load15 = Math.round(vals["state"]["device"]["load15min"] * 100) / 100
this.statOs.totalram = vals["state"]["device"]["totalram"]
this.statOs.freeram = vals["state"]["device"]["freeram"]
},
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
this.statRx.packetsDummy = 0
})
},
// ========== include end from 'common/monitoring-methods.js.j2'
// ========== include from 'common/setup-methods.js.j2'
// ========== include end from 'common/setup-methods.js.j2'
// ========== include from 'common/admin-methods.js.j2'
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
},
doModemReboot() {
if (this.submitStatus.modemReboot !== null) {
return
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'dpdi', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'
performUpdateSettings() {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
let vals = await d.json()
this.settingFetchComplete = true
this.updateRxtxSettings(vals)
this.updateBuclnbSettings(vals)
this.updateNetworkSettings(vals)
if ('netServerName' in vals['settings']) {
document.getElementsByTagName('title')[0].innerText = vals['settings']['netServerName']
}
}
doFetchSettings().then(() => {})
}
},
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", { credentials: 'same-origin' })
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["firmware"]["version"]
this.about.modemUid = d["firmware"]["modemId"]
this.about.modemSn = d["firmware"]["modemSn"]
this.about.macManagement = d["firmware"]["macMang"]
this.about.macData = d["firmware"]["macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
});
app.mount('#app')
</script>
</body>
</html>