Compare commits

..

60 Commits

Author SHA1 Message Date
479200df9e изменил формат аптайма 2025-01-09 12:49:59 +03:00
20cf08e2a1 добавил получение sysinfo и вывод аптайма в веб-морде 2025-01-09 12:02:42 +03:00
ab654a754c патч для переподключения к API в случае ошибок 2025-01-09 11:28:35 +03:00
8990fed8f0 зачатки страницы для разработчиков 2024-12-28 16:42:14 +03:00
be6c8023c5 добавил защиту от двойного обновления прошивки, добавил настройки TCP-акселерации в веб (они пока не работает из-за API) 2024-12-28 16:41:58 +03:00
24cb1061a7 добавил отключенную скорость кода 3/5 2024-12-28 11:20:37 +03:00
77ba05e407 косметические исправления: увеличил шрифт, убрал жирный шрифт в таблицах 2024-12-25 11:05:22 +03:00
f3454897d8 исправление полей в SCPC модеме 2024-12-24 17:21:04 +03:00
dc4e37eb8a Merge branch 'refs/heads/dev-scpc-new-rolloff' 2024-12-23 13:57:59 +03:00
522abee794 косметические исправления страниц 2024-12-23 12:52:52 +03:00
8278e4119f косметические исправления страниц 2024-12-23 09:48:02 +03:00
0a3a282d0f некоторые нововведения в front-generator 2024-12-23 09:40:43 +03:00
3d97824ee7 фикс модкода CCM 2024-12-23 09:39:50 +03:00
55fa498dc1 обновил поле rolloff 2024-12-16 09:39:39 +03:00
f5c5caa31c добавил зачатки front-generator, исправил мелкий баг QoS 2024-12-16 09:30:16 +03:00
833374a80e сделал API статической библиотекой 2024-11-29 17:52:25 +03:00
b67011b9a3 добавил получение статуса из API вместе со статистикой 2024-11-29 17:37:25 +03:00
91e9c0301e фикс: билд SCPC модема 2024-11-29 15:12:27 +03:00
98dcc06a6a логгирование ошибок API (установка параметров) 2024-11-29 15:04:38 +03:00
e0aacfe8aa логгирование ошибок API на всех периодических запросах 2024-11-29 14:11:36 +03:00
3e4ffc8281 изменил логику подключения к API 2024-11-29 13:36:39 +03:00
1f8ea04f43 добавил динамическую линковку для boost::log 2024-11-28 17:43:41 +03:00
ad6d734f4a скрыл ржачную картинку 2024-11-28 16:58:57 +03:00
6d79d60eb1 исправил надпись 2024-11-28 16:56:34 +03:00
572a2583f0 логика работы TDMA 2024-11-28 16:56:20 +03:00
925fec6dda добавил фронт для TDMA 2024-11-28 14:57:29 +03:00
5f3d5791da исправление замечаний от Кости 2024-11-27 11:20:55 +03:00
c05a9cff7a добавил повторные попытки подключения к API, если не удалось подключиться 2024-11-15 15:49:20 +03:00
dff0ba1cd3 фикс: центральная частота 2024-11-15 15:37:08 +03:00
43f35da9a2 фикс: цвета в темной теме 2024-11-15 15:22:56 +03:00
ac04c0545b фикс: версия ПО и прочее не показывалось в вебке 2024-11-15 15:12:19 +03:00
3e46f82c0e фикс: билда релизной версии 2024-11-15 14:30:02 +03:00
1536914888 фикс: не использовался путь для статических файлов + немного поменял цвета 2024-11-15 14:26:30 +03:00
1a80e9d455 фикс: не использовался путь для статических файлов 2024-11-15 14:15:10 +03:00
e2618e0300 обновление vue js 2024-11-15 13:50:14 +03:00
1d73547eae cleanup + изменение цветов темы 2024-11-15 13:45:25 +03:00
4a27a46c27 фикс работы с синхронизацией потоков 2024-11-15 10:55:52 +03:00
55448c2bfe поменял название полей у QoS, чтобы прасер был проще 2024-11-15 10:38:52 +03:00
87725ad20a фикс ошибок: пустая строка qos.class.filters.proto в запросе, не применяющиеся настройки сети, нет шага у "ACM*" и "*ослабление" 2024-11-15 10:13:21 +03:00
cc354b73e3 фикс ошибок: пустая строка qos.class.filters.proto в запросе, не применяющиеся настройки сети, нет шага у "ACM*" и "*ослабление" 2024-11-15 09:50:43 +03:00
200dfef698 мелкие исправления предупреждений 2024-11-14 17:30:33 +03:00
5ab16a89db фикс действий с системой 2024-11-14 16:59:41 +03:00
e27164a8b3 исправил шаг в полях с частотой 2024-11-14 16:45:54 +03:00
ccc7766e88 кучка мелких фиксов + добавление перезагрузки модема и сброса настроек 2024-11-14 16:42:24 +03:00
6d076f03cd сделал загрузку обновленной прошивки и само обновление раздельными 2024-11-14 15:42:05 +03:00
ed1bd12c95 фикс бага с настройками сети в отладочной версии API 2024-11-14 15:18:18 +03:00
515a05ec9b добавил получение версии ПО 2024-11-14 15:10:32 +03:00
eda26319c4 добавил sha256 для файла обновления 2024-11-14 11:34:28 +03:00
0dcc562b7d добавил обновление прошивки из веб морды 2024-11-14 11:09:53 +03:00
6467333846 рефактор параметров QoS и tcp-акселерации 2024-11-14 09:48:57 +03:00
484a6abe08 добавил все настройки в веб, сделал cleanup интерфейса 2024-11-13 17:44:42 +03:00
9577ac844d исправления типов параметров в JS 2024-11-13 11:37:06 +03:00
ed0bfce64d фикс http keep alive 2024-11-13 11:29:31 +03:00
8da8c054bf фикс ошибок релизной сборки и парсинга большого тела запроса 2024-11-13 11:25:48 +03:00
90b1f221ea фикс ошибок применения rx/tx параметров 2024-11-12 17:04:06 +03:00
df4b990316 добавил изменение параметров rx/tx + изменил верстку блоков с настройками 2024-11-12 17:01:17 +03:00
dc2d464f41 добавил изменение параметров dpdi (CinC) 2024-11-12 15:37:58 +03:00
087da149f1 изменил политику кеширования, убрал строковые константы из статистик (пилоты, размер кадра) 2024-11-12 14:13:07 +03:00
c0e7e1e300 окно подтверждения применения настроек BUC и LNB, фикс нулевой длинны HTTP запроса 2024-11-12 13:34:59 +03:00
800473b4e3 окно подтверждения применения настроек BUC и LNB 2024-11-12 13:22:58 +03:00
33 changed files with 5086 additions and 553 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ cmake-build-*
cert.pem cert.pem
key.pem key.pem
dh.pem dh.pem
/web-action

View File

@@ -15,6 +15,16 @@ else()
message(FATAL_ERROR "You must set build type \"Debug\" or \"Release\". Another build types not supported!") message(FATAL_ERROR "You must set build type \"Debug\" or \"Release\". Another build types not supported!")
endif() endif()
if("${MODEM_TYPE}" STREQUAL "SCPC")
add_definitions(-DMODEM_IS_SCPC)
message(STATUS "Selected SCPC modem")
elseif ("${MODEM_TYPE}" STREQUAL "TDMA")
add_definitions(-DMODEM_IS_TDMA)
message(STATUS "Selected TDMA modem")
else()
message(FATAL_ERROR "You must set `MODEM_TYPE` \"SCPC\" or \"TDMA\"!")
endif()
add_compile_options(-Wall -Wextra -Wsign-conversion) add_compile_options(-Wall -Wextra -Wsign-conversion)
# максимальный размер тела запроса 200mb # максимальный размер тела запроса 200mb
@@ -47,6 +57,8 @@ add_executable(terminal-web-server
src/auth/utils.h src/auth/utils.h
) )
add_definitions(-DBOOST_LOG_DYN_LINK)
find_package(Boost 1.53.0 COMPONENTS system thread filesystem log log_setup REQUIRED) find_package(Boost 1.53.0 COMPONENTS system thread filesystem log log_setup REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(terminal-web-server ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} terminal-client-api) target_link_libraries(terminal-web-server ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} terminal-client-api)

View File

@@ -14,15 +14,15 @@ include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS -fPIC) set(CMAKE_CXX_FLAGS -fPIC)
set(default_build_type "Release") set(default_build_type "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -fprofile-arcs -ftest-coverage") set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -s -DNDEBUG ") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -s -DNDEBUG ")
message(${CMAKE_CXX_FLAGS}) message(${CMAKE_CXX_FLAGS})
message("CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE}) message("CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
message(${CMAKE_CXX_FLAGS}) message(${CMAKE_CXX_FLAGS})
add_library(terminal-client-api SHARED add_library(terminal-client-api STATIC
client/main.cpp client/main.cpp
client/sock_client.cpp client/sock_client.cpp
client/system_client.cpp client/system_client.cpp

View File

@@ -57,6 +57,7 @@ struct modulator_settings_com{
bool is_carrier; bool is_carrier;
bool tx_is_on; bool tx_is_on;
bool is_cinc; bool is_cinc;
uint32_t modcod_tx;
}; };
struct modulator_state_com{ struct modulator_state_com{
@@ -476,7 +477,7 @@ struct cmd_modulator_settings
archive(modulator_settings.attenuation,modulator_settings.baudrate, modulator_settings.central_freq_in_kGz, archive(modulator_settings.attenuation,modulator_settings.baudrate, modulator_settings.central_freq_in_kGz,
modulator_settings.is_carrier, modulator_settings.is_cinc, modulator_settings.is_carrier, modulator_settings.is_cinc,
modulator_settings.is_save_current_state, modulator_settings.is_test_data, modulator_settings.is_save_current_state, modulator_settings.is_test_data,
modulator_settings.rollof, modulator_settings.tx_is_on); modulator_settings.rollof, modulator_settings.tx_is_on, modulator_settings.modcod_tx);
} }
}; };

View File

@@ -139,6 +139,7 @@ struct modulator_settings{
bool is_carrier; bool is_carrier;
bool tx_is_on; bool tx_is_on;
bool is_cinc; bool is_cinc;
uint32_t modcod_tx;
}; };
EXTERNC CP_Result CP_SetModulatorSettings(TSID sid, modulator_settings& settings); EXTERNC CP_Result CP_SetModulatorSettings(TSID sid, modulator_settings& settings);

View File

@@ -0,0 +1,75 @@
{
"monitoring-params": {},
"params": {
"rxtx": {
"rx.en": {
"model": "w:switch",
"label": "Включить передатчик"
},
"rx.isTestInputData": {
"model": "w:select",
"label": "Включить передатчик",
"items": [
{"value": "false", "label": "Ethernet"},
{"value": "true", "label": "Тест (CW)"}
]
},
"rx.freqKhz": {
"model": "w:number",
"number.type": "int",
"number.step": 1,
"number.min": 500000,
"number.max": 15000000
}
}
},
"modem_types": {
"tdma": {
"modem_name": "RCSM-101 TDMA",
"groupsList": ["rxtx"],
"tabs": [
{
"name": "monitoring",
"desc": "Мониторинг"
},
{
"name": "setup",
"desc": "Настройки",
"widgets": [
{"group": "html", "name": "h3", "payload": "Настройки передатчика"},
{"group": "rxtx", "name": "rx.en"},
{"group": "rxtx", "name": "rx.isTestInputData"},
{"group": "html", "name": "h3", "payload": "Параметры передачи"},
{"group": "rxtx", "name": "rx.freqKhz"}
]
},
{
"name": "admin",
"desc": "Администрирование"
}
]
},
"scpc": {
"modem_name": "RCSM-101",
"groupsList": ["rxtx"],
"tabs": [
{
"name": "monitoring",
"desc": "Мониторинг"
},
{
"name": "setup",
"desc": "Настройки"
},
{
"name": "qos",
"desc": "QoS"
},
{
"name": "admin",
"desc": "Администрирование"
}
]
}
}
}

39
front-generator/render.py Normal file
View File

@@ -0,0 +1,39 @@
import json
from jinja2 import Environment, FileSystemLoader
import sys
def build_modem_env(modem):
with open('render-params.json') as f:
config = json.load(f)
if modem not in config['modem_types']:
raise RuntimeError(f"Modem '{modem}' is not exist in config!")
mc = config['modem_types'][modem]
return {
"modem_name": mc['modem_name'],
"header_tabs": mc['tabs'],
"js_tabs_array": str([t['name'] for t in mc['tabs']]),
"params": {"groupsList": mc["groupsList"]} | config["params"]
}
def render_modem(modem):
loader = FileSystemLoader('template')
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template('main.html')
context = build_modem_env(modem)
with open(f"main-{modem}.html", "w") as f:
f.write(template.render(context))
if __name__ == '__main__':
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <scpc|tdma>")
render_modem(sys.argv[1])

View File

@@ -0,0 +1,139 @@
{% raw %}// для обновления высоты хидера
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 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' }
}
{% endraw %}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
isCinC: false,
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
{% for pg in params.groupsList %}
{{ pg }}: false,
{% endfor %}
firmwareUpload: false,
firmwareUpgrade: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
stat: {
}
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,

View File

@@ -98,9 +98,9 @@ namespace http::auth {
~AuthRequiredResource() override; ~AuthRequiredResource() override;
private: private:
uint32_t perms;
resource::respGenerator generator_;
AuthProvider& provider_; AuthProvider& provider_;
resource::respGenerator generator_;
uint32_t perms;
}; };
} }

View File

@@ -9,9 +9,12 @@
std::string http::utils::sha256(const std::string &payload) { std::string http::utils::sha256(const std::string &payload) {
// Вычисляем SHA256 хеш return sha256(payload.c_str(), payload.size());
}
std::string http::utils::sha256(const char* data, size_t size) {
unsigned char hash[SHA256_DIGEST_LENGTH]; unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length(), hash); SHA256(reinterpret_cast<const unsigned char *>(data), size, hash);
// Преобразуем хеш в шестнадцатеричную строку // Преобразуем хеш в шестнадцатеричную строку
std::stringstream ss; std::stringstream ss;

View File

@@ -6,6 +6,7 @@
namespace http::utils { namespace http::utils {
std::string sha256(const std::string& payload); std::string sha256(const std::string& payload);
std::string sha256(const char* data, size_t size);
std::string sha256AsB64(const std::string& payload); std::string sha256AsB64(const std::string& payload);
std::string b64Encode(const char* data, size_t size); std::string b64Encode(const char* data, size_t size);

View File

@@ -19,10 +19,14 @@
#include "terminal_api_driver.h" #include "terminal_api_driver.h"
#include "auth/resources.h" #include "auth/resources.h"
#include "auth/jwt.h" #include "auth/jwt.h"
#include "auth/utils.h"
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
constexpr const char* REBOOT_COMMAND = "web-action reboot";
constexpr const char* UPGRADE_COMMAND = "web-action upgrade";
static std::vector<char> loadFile(const std::string& path) { static std::vector<char> loadFile(const std::string& path) {
std::ifstream is(path, std::ios::in | std::ios::binary); std::ifstream is(path, std::ios::in | std::ios::binary);
@@ -80,42 +84,59 @@ class ServerResources {
std::unique_ptr<api_driver::ApiDriver> api; std::unique_ptr<api_driver::ApiDriver> api;
http::auth::AuthProvider auth{}; http::auth::AuthProvider auth{};
bool upgradeOrRebootRunning = false;
static void onUploadFirmware(const http::server::Request& req) {
std::ofstream f("/tmp/firmware.zip", std::ios::binary);
if (f.is_open()) {
f.write(req.payload.data(), static_cast<long>(req.payload.size()));
f.close();
} else {
throw std::runtime_error("File is not open");
}
}
void doTerminalUpgrade() const {
api->executeInApi([](TSID sid) {
CP_SetDmaDebug(sid, "begin_save_config", "");
system(UPGRADE_COMMAND);
CP_SetDmaDebug(sid, "save_config", "");
});
}
public: public:
static constexpr const char* INDEX_HTML = "static/main.html"; #if defined(MODEM_IS_TDMA)
static constexpr const char* LOGIN_HTML = "static/login.html"; static constexpr const char* INDEX_HTML = "/main-tdma.html";
#elif defined(MODEM_IS_SCPC)
static constexpr const char* INDEX_HTML = "/main-scpc.html";
#else
#error "Modem type not defined!"
#endif
static constexpr const char* LOGIN_HTML = "/login.html";
// картинки, их даже можно кешировать // картинки, их даже можно кешировать
static constexpr const char* FAVICON_ICO = "static/favicon.png"; static constexpr const char* FAVICON_ICO = "/favicon.ico";
static constexpr const char* KROKODIL_GIF = "static/krokodil.gif"; static constexpr const char* VUE_JS = "/js/vue.js"; // это тоже можно кешировать
static constexpr const char* VUE_JS = "static/js/vue.js"; // это тоже можно кешировать
// а эти стили нельзя кешировать в отладочной версии // а эти стили нельзя кешировать в отладочной версии
static constexpr const char* STYLE_CSS = "static/style.css"; static constexpr const char* STYLE_CSS = "/style.css";
static constexpr const char* FIELDS_CSS = "static/fields.css"; static constexpr const char* FIELDS_CSS = "/fields.css";
static constexpr const char* KB_MP4 = "static/video_2024-11-06_15-49-35.mp4"; static constexpr const char* INTERNET_JPG = "/internet.jpg";
ServerResources(const ServerResources&) = delete; ServerResources(const ServerResources&) = delete;
ServerResources(): sf(std::make_unique<http::resource::StaticFileFactory>()), api(std::make_unique<api_driver::ApiDriver>()) { explicit ServerResources(const std::string& staticFilesPath): sf(std::make_unique<http::resource::StaticFileFactory>()), api(std::make_unique<api_driver::ApiDriver>()) {
api->startDaemon(); api->startDaemon();
auth.users.emplace_back(std::make_shared<http::auth::User>("admin", "", http::auth::User::SUPERUSER)); auth.users.emplace_back(std::make_shared<http::auth::User>("admin", "", http::auth::User::SUPERUSER));
sf->registerFile(FAVICON_ICO, mime_types::image_png, true); sf->registerFile(staticFilesPath + "/favicon.png", FAVICON_ICO, mime_types::image_png, true);
sf->registerFile(KROKODIL_GIF, mime_types::image_gif, true); sf->registerFile(staticFilesPath + VUE_JS, VUE_JS, mime_types::javascript, true);
sf->registerFile(VUE_JS, mime_types::javascript, true); sf->registerFile(staticFilesPath + STYLE_CSS, STYLE_CSS, mime_types::text_css, true);
sf->registerFile(KB_MP4, mime_types::video_mp4, true); sf->registerFile(staticFilesPath + FIELDS_CSS, FIELDS_CSS, mime_types::text_css, true);
sf->registerFile(staticFilesPath + INDEX_HTML, INDEX_HTML, mime_types::text_html, false);
#if USE_DEBUG sf->registerFile(staticFilesPath + LOGIN_HTML, LOGIN_HTML, mime_types::text_html, true);
constexpr bool allowCacheCss = false; sf->registerFile(staticFilesPath + INTERNET_JPG, INTERNET_JPG, mime_types::image_jpeg, true);
#else
constexpr bool allowCacheCss = true;
#endif
sf->registerFile(INDEX_HTML, mime_types::text_html, allowCacheCss);
sf->registerFile(LOGIN_HTML, mime_types::text_html, allowCacheCss);
sf->registerFile(STYLE_CSS, mime_types::text_css, allowCacheCss);
sf->registerFile(FIELDS_CSS, mime_types::text_css, allowCacheCss);
} }
void registerResources(http::server::Server& s) { void registerResources(http::server::Server& s) {
@@ -160,7 +181,7 @@ public:
http::server::stockReply(http::server::bad_request, rep); http::server::stockReply(http::server::bad_request, rep);
} }
})); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/logout", [this](const auto& req, auto& rep) { s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/logout", [](const auto& req, auto& rep) {
if (req.method == "GET") { if (req.method == "GET") {
http::server::httpRedirect(rep, "/login"); http::server::httpRedirect(rep, "/login");
rep.headers.push_back({.name = "Set-Cookie", .value = http::auth::jwt::EMPTY_AUTH_COOKIE}); rep.headers.push_back({.name = "Set-Cookie", .value = http::auth::jwt::EMPTY_AUTH_COOKIE});
@@ -169,12 +190,11 @@ public:
} }
})); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/favicon.ico", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FAVICON_ICO, rep); })); s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(FAVICON_ICO, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FAVICON_ICO, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/images/krokodil_vzryvaetsya_hd.gif", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(KROKODIL_GIF, rep); })); s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(STYLE_CSS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(STYLE_CSS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/style.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(STYLE_CSS, rep); })); s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(FIELDS_CSS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FIELDS_CSS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/fields.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FIELDS_CSS, rep); })); s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(VUE_JS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(VUE_JS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/js/vue.js", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(VUE_JS, rep); })); s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(INTERNET_JPG, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(INTERNET_JPG, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/vid/video_2024-11-06_15-49-35.mp4", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(KB_MP4, rep); }));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/statistics", this->auth, http::auth::User::WATCH_STATISTICS, [this](const auto& req, auto& rep) { s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/statistics", this->auth, http::auth::User::WATCH_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "GET") { if (req.method != "GET") {
@@ -186,6 +206,8 @@ public:
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"mainState":)"; std::string result = R"({"mainState":)";
result += api->loadTerminalState(); result += api->loadTerminalState();
result += R"(,"sysinfo":)";
result += api->loadSysInfo();
result += "}"; result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
})); }));
@@ -204,6 +226,18 @@ public:
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
})); }));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/aboutFirmware", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const auto result = api->loadFirmwareVersion();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetPacketStatistics", this->auth, http::auth::User::RESET_PACKET_STATISTICS, [this](const auto& req, auto& rep) { s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetPacketStatistics", this->auth, http::auth::User::RESET_PACKET_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "POST") { if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep); http::server::stockReply(http::server::bad_request, rep);
@@ -272,7 +306,188 @@ public:
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} }
})); }));
#ifdef MODEM_IS_SCPC
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/cinc", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setCincSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/cinc): Can't set CinC settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
}));
#endif
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/rxtx", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setRxTxSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/rxtx): Can't set RX/TX settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/network", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setNetworkSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/rxtx): Can't set RX/TX settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/debugSend", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setDebugSendSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/rxtx): Can't set RX/TX settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/reboot", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
this->upgradeOrRebootRunning = true;
system(REBOOT_COMMAND);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetSettings", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
api->resetDefaultSettings();
system(REBOOT_COMMAND);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/firmwareUpdate", this->auth, http::auth::User::UPDATE_FIRMWARE, [this](const auto& req, auto& rep) {
if (req.method != "PUT") {
http::server::stockReply(http::server::bad_request, rep);
}
this->upgradeOrRebootRunning = true;
onUploadFirmware(req);
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"status":"ok","fwsize":)";
result += std::to_string(req.payload.size());
result += R"(,"sha256":")";
result += http::utils::sha256(req.payload.data(), req.payload.size());
result += "\"}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
this->upgradeOrRebootRunning = false;
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/doFirmwareUpgrade", this->auth, http::auth::User::UPDATE_FIRMWARE, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
this->upgradeOrRebootRunning = true;
doTerminalUpgrade();
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const auto result = api->loadFirmwareVersion();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
boost::ignore_unused(req);
sf->serve(INTERNET_JPG, rep);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev/fetchParams", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"status":"ok","fwsize":)";
result += std::to_string(req.payload.size());
result += R"(,"sha256":")";
result += "\"}";
}));
} }
~ServerResources() = default; ~ServerResources() = default;
@@ -286,9 +501,9 @@ int main(int argc, char *argv[]) {
if (argc != 4 && argc != 5) { if (argc != 4 && argc != 5) {
std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\n"; std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\n";
std::cerr << " For IPv4, try:\n"; std::cerr << " For IPv4, try:\n";
std::cerr << " receiver nossl 0.0.0.0 80\n"; std::cerr << " receiver nossl 0.0.0.0 80 .\n";
std::cerr << " For IPv6, try:\n"; std::cerr << " For IPv6, try:\n";
std::cerr << " receiver nossl 0::0 80\n"; std::cerr << " receiver nossl 0::0 80 .\n";
return 1; return 1;
} }
@@ -309,7 +524,9 @@ int main(int argc, char *argv[]) {
BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey; BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey;
#endif #endif
ServerResources resources; const std::string staticFilesPath = (argc == 5 ? argv[4]: ".");
BOOST_LOG_TRIVIAL(info) << "Use static files path: " << staticFilesPath << "/";
ServerResources resources(staticFilesPath);
// Initialise the server. // Initialise the server.
std::unique_ptr<http::server::Server> s; std::unique_ptr<http::server::Server> s;

View File

@@ -13,6 +13,8 @@ namespace http::server {
} }
void Connection::start() { void Connection::start() {
request_parser_.reset();
request_.reset();
doRead(); doRead();
} }
@@ -23,9 +25,6 @@ namespace http::server {
Connection::~Connection() = default; Connection::~Connection() = default;
void Connection::doRead() { void Connection::doRead() {
request_parser_.reset();
request_.reset();
auto self(shared_from_this()); auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) { socket_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) {
if (!ec) { if (!ec) {
@@ -38,6 +37,7 @@ namespace http::server {
doWrite(); doWrite();
} else if (result == RequestParser::bad) { } else if (result == RequestParser::bad) {
stockReply(bad_request, reply_); stockReply(bad_request, reply_);
needClose = true;
doWrite(); doWrite();
} else { } else {
doRead(); doRead();
@@ -59,8 +59,10 @@ namespace http::server {
auto self(shared_from_this()); auto self(shared_from_this());
async_write(socket_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) { async_write(socket_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) { if (!ec && !needClose) {
// keep alive Connection // keep alive Connection
request_parser_.reset();
request_.reset();
doRead(); doRead();
} else { } else {
connection_manager_.stop(shared_from_this()); connection_manager_.stop(shared_from_this());
@@ -74,6 +76,8 @@ namespace http::server {
void SslConnection::start() { void SslConnection::start() {
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
request_parser_.reset();
request_.reset();
// Perform the SSL handshake // Perform the SSL handshake
stream_.async_handshake(boost::asio::ssl::stream_base::server, boost::beast::bind_front_handler([this](auto ec) { stream_.async_handshake(boost::asio::ssl::stream_base::server, boost::beast::bind_front_handler([this](auto ec) {
@@ -83,6 +87,11 @@ namespace http::server {
} }
void SslConnection::stop() { void SslConnection::stop() {
try {
stream_.shutdown();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "SslConnection::stop(): Can't shutdown ssl socket: " << e.what();
}
} }
SslConnection::~SslConnection() = default; SslConnection::~SslConnection() = default;
@@ -90,9 +99,6 @@ namespace http::server {
void SslConnection::doRead() { void SslConnection::doRead() {
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
request_parser_.reset();
request_.reset();
auto self(shared_from_this()); auto self(shared_from_this());
stream_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) { stream_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) {
if (!ec) { if (!ec) {
@@ -105,6 +111,7 @@ namespace http::server {
doWrite(); doWrite();
} else if (result == RequestParser::bad) { } else if (result == RequestParser::bad) {
stockReply(bad_request, reply_); stockReply(bad_request, reply_);
needClose = true;
doWrite(); doWrite();
} else { } else {
doRead(); doRead();
@@ -126,8 +133,10 @@ namespace http::server {
auto self(shared_from_this()); auto self(shared_from_this());
async_write(stream_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) { async_write(stream_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) { if (!ec && !needClose) {
// keep alive Connection // keep alive Connection
request_parser_.reset();
request_.reset();
doRead(); doRead();
} else { } else {
connection_manager_.stop(shared_from_this()); connection_manager_.stop(shared_from_this());

View File

@@ -72,6 +72,8 @@ namespace http::server {
/// The reply to be sent back to the client. /// The reply to be sent back to the client.
Reply reply_; Reply reply_;
bool needClose = false;
}; };
class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> { class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> {
@@ -114,6 +116,8 @@ namespace http::server {
/// The reply to be sent back to the client. /// The reply to be sent back to the client.
Reply reply_; Reply reply_;
bool needClose = false;
}; };
typedef std::shared_ptr<ConnectionBase> connection_ptr; typedef std::shared_ptr<ConnectionBase> connection_ptr;

View File

@@ -1,11 +1,30 @@
#include "request_parser.hpp" #include "request_parser.hpp"
#include <sstream> #include <sstream>
#include "request.hpp" #include "request.hpp"
namespace http::server { namespace http::server {
constexpr int HTTP_MAX_HEADERS = 64;
/**
* Функция, позволяющая или запрещающая выделение размера тела для запросов.
* @return true, если тело удовлетворяет размерам
*/
static bool requestBodySizeResolver(Request& req, size_t reqSize) {
// разрешаем тело только для POST запросов
if (req.method == "POST") {
return reqSize < 0x4000; // 16кб на все POST-запросы к API будет более чем достаточно
}
// это для обновления прошивки
if (req.method == "PUT" && req.url->path == "/api/firmwareUpdate") {
return reqSize <= HTTP_MAX_PAYLOAD;
}
return false;
}
static void parseParams(Url& u, const std::string& query) { static void parseParams(Url& u, const std::string& query) {
std::istringstream iss(query); std::istringstream iss(query);
std::string param; std::string param;
@@ -72,7 +91,7 @@ namespace http::server {
switch (state_) { switch (state_) {
case expecting_payload: case expecting_payload:
req.payload.push_back(input); req.payload.push_back(input);
if (req.payload.size() <= contentLenghtHeader - 1) { if (req.payload.size() < contentLenghtHeader) {
return indeterminate; return indeterminate;
} }
return good; return good;
@@ -186,17 +205,23 @@ namespace http::server {
if (input == '\r') { if (input == '\r') {
state_ = expecting_newline_3; state_ = expecting_newline_3;
return indeterminate; return indeterminate;
} else if (!req.headers.empty() && (input == ' ' || input == '\t')) { }
if (!req.headers.empty() && (input == ' ' || input == '\t')) {
state_ = header_lws; state_ = header_lws;
return indeterminate; return indeterminate;
} else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
} else {
req.headers.emplace_back();
req.headers.back().name.push_back(input);
state_ = header_name;
return indeterminate;
} }
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
if (req.headers.size() > HTTP_MAX_HEADERS) {
return bad;
}
req.headers.emplace_back();
req.headers.back().name.push_back(input);
state_ = header_name;
return indeterminate;
case header_lws: case header_lws:
if (input == '\r') { if (input == '\r') {
state_ = expecting_newline_2; state_ = expecting_newline_2;
@@ -251,12 +276,14 @@ namespace http::server {
if (content_len.empty()) { if (content_len.empty()) {
return good; return good;
} }
contentLenghtHeader = std::stol(content_len); contentLenghtHeader = std::stoul(content_len);
state_ = expecting_payload; if (contentLenghtHeader == 0) {
if (contentLenghtHeader > HTTP_MAX_PAYLOAD) { return good;
return bad; }
if (requestBodySizeResolver(req, contentLenghtHeader)) {
state_ = expecting_payload;
return indeterminate;
} }
return indeterminate;
} }
return bad; return bad;

View File

@@ -8,7 +8,7 @@
namespace http::server { namespace http::server {
struct Request; class Request;
/// Parser for incoming requests. /// Parser for incoming requests.
class RequestParser { class RequestParser {

View File

@@ -3,8 +3,6 @@
#include <fstream> #include <fstream>
#include <utility> #include <utility>
#include "../../dependencies/control_system/common/protocol_commands.h"
static void loadFile(const std::string& path, std::vector<char>& content) { static void loadFile(const std::string& path, std::vector<char>& content) {
std::ifstream is(path, std::ios::in | std::ios::binary); std::ifstream is(path, std::ios::in | std::ios::binary);
if (!is) { if (!is) {
@@ -24,32 +22,47 @@ static void loadFile(const std::string& path, std::vector<char>& content) {
http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {} http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {}
http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache): path(std::move(path)), type(type), allowCache(allowCache) { http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(const std::string& path, std::string webPath, server::mime_types::Mime type, bool allowCache):
webPath(std::move(webPath)),
#ifdef USE_DEBUG
fsPath(path),
#endif
type(type), allowCache(allowCache) {
#ifdef USE_DEBUG
if (allowCache) { if (allowCache) {
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path; BOOST_LOG_TRIVIAL(info) << "Load static file " << this->webPath;
loadFile(this->path, this->content); loadFile(path, this->content);
} else { } else {
BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->path; BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->webPath;
} }
#else
BOOST_LOG_TRIVIAL(info) << "Load static file " << path;
loadFile(path, this->content);
#endif
} }
http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default; http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default;
http::resource::StaticFileFactory::StaticFileFactory() = default; http::resource::StaticFileFactory::StaticFileFactory() = default;
void http::resource::StaticFileFactory::registerFile(const std::string &path, server::mime_types::Mime type, bool allowCache) { void http::resource::StaticFileFactory::registerFile(const std::string &path, const std::string &webPath, server::mime_types::Mime type, bool allowCache) {
this->files.emplace_back(path, type, allowCache); this->files.emplace_back(path, webPath, type, allowCache);
} }
void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) { void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) {
for (auto& f: this->files) { for (auto& f: this->files) {
if (f.path == path) { if (f.webPath == path) {
#ifdef USE_DEBUG
if (f.allowCache) { if (f.allowCache) {
rep.content.clear(); rep.content.clear();
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end()); rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
} else { } else {
BOOST_LOG_TRIVIAL(debug) << "Reload file " << path << " (http path: " << path << ")"; BOOST_LOG_TRIVIAL(debug) << "Reload file " << path << " (http path: " << path << ")";
loadFile(f.path, rep.content); loadFile(f.fsPath, rep.content);
} }
#else
rep.content.clear();
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
#endif
rep.status = server::ok; rep.status = server::ok;
// rep.headers.clear(); // rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = server::mime_types::toString(f.type)}); rep.headers.push_back({.name = "Content-Type", .value = server::mime_types::toString(f.type)});

View File

@@ -23,9 +23,12 @@ namespace http::resource {
class StaticFileFactory { class StaticFileFactory {
class StaticFileDef { class StaticFileDef {
public: public:
StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache = true); StaticFileDef(const std::string& path, std::string webPath, server::mime_types::Mime type, bool allowCache = true);
std::string path; std::string webPath;
#ifdef USE_DEBUG
std::string fsPath;
#endif
server::mime_types::Mime type; server::mime_types::Mime type;
bool allowCache; bool allowCache;
std::vector<char> content; std::vector<char> content;
@@ -36,7 +39,7 @@ namespace http::resource {
public: public:
StaticFileFactory(); StaticFileFactory();
void registerFile(const std::string& path, server::mime_types::Mime type, bool allowCache = true); void registerFile(const std::string& path, const std::string &webPath, server::mime_types::Mime type, bool allowCache = true);
void serve(const std::string& path, server::Reply& rep); void serve(const std::string& path, server::Reply& rep);

File diff suppressed because it is too large Load Diff

View File

@@ -39,16 +39,19 @@ namespace api_driver {
std::string loadSettings() const; std::string loadSettings() const;
std::string loadFirmwareVersion() const;
/** /**
* Установить настройки RX/TX, readback можно получить используя loadTerminalState * Установить настройки RX/TX, readback можно получить используя loadTerminalState
*/ */
void setRxTxSettings(boost::property_tree::ptree &pt); void setRxTxSettings(boost::property_tree::ptree &pt);
#ifdef MODEM_IS_SCPC
/** /**
* Установить настройки CinC, readback можно получить используя loadTerminalState. * Установить настройки CinC, readback можно получить используя loadTerminalState.
*/ */
void setCincSettings(boost::property_tree::ptree &pt); void setCincSettings(boost::property_tree::ptree &pt);
#endif
/** /**
* Установить настройки BUC и LNB, readback можно получить используя loadTerminalState. * Установить настройки BUC и LNB, readback можно получить используя loadTerminalState.
*/ */
@@ -59,16 +62,20 @@ namespace api_driver {
*/ */
void setQosSettings(boost::property_tree::ptree &pt); void setQosSettings(boost::property_tree::ptree &pt);
void setNetworkSettings(boost::property_tree::ptree & pt);
void setDebugSendSettings(boost::property_tree::ptree & pt);
void resetDefaultSettings();
void executeInApi(const std::function<void(TSID sid)>& callback);
static std::string loadSysInfo();
~ApiDriver(); ~ApiDriver();
private: private:
TSID sid{0};
unsigned int access{0};
std::string deviceInitState;
std::unique_ptr<TerminalApiDaemon> daemon; std::unique_ptr<TerminalApiDaemon> daemon;
bool getIsCinC() const;
}; };
} }

16
static/dev-params.json Normal file
View File

@@ -0,0 +1,16 @@
{
"params": [
{
"label": "Запись пакетов",
"name": "log_bool",
"widget": "checkbox",
"function": "DmaDebug"
},
{
"label": "Unused test",
"name": "log_bool",
"widget": "checkbox",
"function": "DmaDebug"
}
]
}

10
static/dev.html Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -1,5 +1,5 @@
.tabs-header { .tabs-header {
margin: 0.5em 0; margin: 0.5em 0 3px;
background: var(--brand-bg); background: var(--brand-bg);
} }
.tabs-header > * { .tabs-header > * {
@@ -7,7 +7,6 @@
} }
.tabs-btn { .tabs-btn {
text-decoration: none; text-decoration: none;
font-size: 18px;
border: none; border: none;
padding: 10px 25px; padding: 10px 25px;
text-align: center; text-align: center;
@@ -17,7 +16,7 @@
} }
.tabs-btn.active { .tabs-btn.active {
color: var(--brand-text); color: var(--brand-text);
border-bottom: 3px solid var(--brand-text); border-bottom: 3px solid var(--bg-action);
} }
.tabs-body-item { .tabs-body-item {
padding: 20px 0; padding: 20px 0;
@@ -58,7 +57,7 @@
} }
.action-button { .action-button {
background: var(--brand-bg); background: var(--bg-action);
} }
.nav-bar-element { .nav-bar-element {
@@ -85,11 +84,12 @@
padding: 1em; padding: 1em;
} }
.tabs-item-flex-container th { .settings-set-container th {
text-align: left; text-align: left;
font-weight: normal;
padding-right: 1em; padding-right: 1em;
} }
.tabs-item-flex-container td { .settings-set-container td {
min-width: 10em; min-width: 10em;
} }
.tabs-item-flex-container h2 { .tabs-item-flex-container h2 {
@@ -113,7 +113,7 @@ label {
.settings-set-container input, .settings-set-container select { .settings-set-container input, .settings-set-container select {
margin-top: 0.5em; margin-top: 0.5em;
border: none; border: none;
border-bottom: solid 2px var(--text-color); border-bottom: solid 2px var(--text-color2);
width: 20em; width: 20em;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -121,7 +121,11 @@ label {
.settings-set-container input:focus { .settings-set-container input:focus {
outline: none; outline: none;
border: none; border: none;
border-bottom: solid 2px var(--brand-text); border-bottom: solid 2px var(--bg-action);
}
.settings-set-container input:invalid {
border: solid 1px var(--text-bad);
} }
/* костыль для браузеров, которые некорректно стилизуют элементы option */ /* костыль для браузеров, которые некорректно стилизуют элементы option */
@@ -189,7 +193,7 @@ details > summary {
.toggle-input input[type="checkbox"]:checked + .slider { .toggle-input input[type="checkbox"]:checked + .slider {
left: 25px; left: 25px;
background-color: var(--brand-text); background-color: var(--bg-action);
} }
.toggle-input input[type="checkbox"]:checked + .slider:before { .toggle-input input[type="checkbox"]:checked + .slider:before {

BIN
static/internet.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,6 +1,6 @@
/*! /*!
* Vue.js v2.7.16 * Vue.js v2.7.14
* (c) 2014-2023 Evan You * (c) 2014-2022 Evan You
* Released under the MIT License. * Released under the MIT License.
*/ */
(function (global, factory) { (function (global, factory) {
@@ -82,16 +82,9 @@
return val == null return val == null
? '' ? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, replacer, 2) ? JSON.stringify(val, null, 2)
: String(val); : String(val);
} }
function replacer(_key, val) {
// avoid circular deps from v3
if (val && val.__v_isRef) {
return val.value;
}
return val;
}
/** /**
* Convert an input value to a number for persistence. * Convert an input value to a number for persistence.
* If the conversion fails, return original string. * If the conversion fails, return original string.
@@ -253,7 +246,9 @@
*/ */
function genStaticKeys$1(modules) { function genStaticKeys$1(modules) {
return modules return modules
.reduce(function (keys, m) { return keys.concat(m.staticKeys || []); }, []) .reduce(function (keys, m) {
return keys.concat(m.staticKeys || []);
}, [])
.join(','); .join(',');
} }
/** /**
@@ -730,35 +725,30 @@
}; };
} }
/****************************************************************************** /******************************************************************************
Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */ ***************************************************************************** */
var __assign = function() { var __assign = function() {
__assign = Object.assign || function __assign(t) { __assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) { for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i]; s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
} }
return t; return t;
}; };
return __assign.apply(this, arguments); return __assign.apply(this, arguments);
};
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
}; };
var uid$2 = 0; var uid$2 = 0;
@@ -892,7 +882,7 @@
}); });
var arrayKeys = Object.getOwnPropertyNames(arrayMethods); var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
var NO_INITIAL_VALUE = {}; var NO_INIITIAL_VALUE = {};
/** /**
* In some cases we may want to disable observation inside a component's * In some cases we may want to disable observation inside a component's
* update computation. * update computation.
@@ -951,7 +941,7 @@
var keys = Object.keys(value); var keys = Object.keys(value);
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
var key = keys[i]; var key = keys[i];
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock); defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock);
} }
} }
} }
@@ -988,8 +978,7 @@
/** /**
* Define a reactive property on an Object. * Define a reactive property on an Object.
*/ */
function defineReactive(obj, key, val, customSetter, shallow, mock, observeEvenIfShallow) { function defineReactive(obj, key, val, customSetter, shallow, mock) {
if (observeEvenIfShallow === void 0) { observeEvenIfShallow = false; }
var dep = new Dep(); var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key); var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) { if (property && property.configurable === false) {
@@ -999,10 +988,10 @@
var getter = property && property.get; var getter = property && property.get;
var setter = property && property.set; var setter = property && property.set;
if ((!getter || setter) && if ((!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)) { (val === NO_INIITIAL_VALUE || arguments.length === 2)) {
val = obj[key]; val = obj[key];
} }
var childOb = shallow ? val && val.__ob__ : observe(val, false, mock); var childOb = !shallow && observe(val, false, mock);
Object.defineProperty(obj, key, { Object.defineProperty(obj, key, {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
@@ -1047,7 +1036,7 @@
else { else {
val = newVal; val = newVal;
} }
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock); childOb = !shallow && observe(newVal, false, mock);
{ {
dep.notify({ dep.notify({
type: "set" /* TriggerOpTypes.SET */, type: "set" /* TriggerOpTypes.SET */,
@@ -2510,10 +2499,11 @@
// to the data on the placeholder node. // to the data on the placeholder node.
vm.$vnode = _parentVnode; vm.$vnode = _parentVnode;
// render self // render self
var prevInst = currentInstance;
var prevRenderInst = currentRenderingInstance;
var vnode; var vnode;
try { try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
setCurrentInstance(vm); setCurrentInstance(vm);
currentRenderingInstance = vm; currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement); vnode = render.call(vm._renderProxy, vm.$createElement);
@@ -2537,8 +2527,8 @@
} }
} }
finally { finally {
currentRenderingInstance = prevRenderInst; currentRenderingInstance = null;
setCurrentInstance(prevInst); setCurrentInstance();
} }
// if the returned array contains only a single node, allow it // if the returned array contains only a single node, allow it
if (isArray(vnode) && vnode.length === 1) { if (isArray(vnode) && vnode.length === 1) {
@@ -2803,112 +2793,6 @@
}; };
} }
var activeEffectScope;
var EffectScope = /** @class */ (function () {
function EffectScope(detached) {
if (detached === void 0) { detached = false; }
this.detached = detached;
/**
* @internal
*/
this.active = true;
/**
* @internal
*/
this.effects = [];
/**
* @internal
*/
this.cleanups = [];
this.parent = activeEffectScope;
if (!detached && activeEffectScope) {
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1;
}
}
EffectScope.prototype.run = function (fn) {
if (this.active) {
var currentEffectScope = activeEffectScope;
try {
activeEffectScope = this;
return fn();
}
finally {
activeEffectScope = currentEffectScope;
}
}
else {
warn$2("cannot run an inactive effect scope.");
}
};
/**
* This should only be called on non-detached scopes
* @internal
*/
EffectScope.prototype.on = function () {
activeEffectScope = this;
};
/**
* This should only be called on non-detached scopes
* @internal
*/
EffectScope.prototype.off = function () {
activeEffectScope = this.parent;
};
EffectScope.prototype.stop = function (fromParent) {
if (this.active) {
var i = void 0, l = void 0;
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].teardown();
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]();
}
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true);
}
}
// nested scope, dereference from parent to avoid memory leaks
if (!this.detached && this.parent && !fromParent) {
// optimized O(1) removal
var last = this.parent.scopes.pop();
if (last && last !== this) {
this.parent.scopes[this.index] = last;
last.index = this.index;
}
}
this.parent = undefined;
this.active = false;
}
};
return EffectScope;
}());
function effectScope(detached) {
return new EffectScope(detached);
}
/**
* @internal
*/
function recordEffectScope(effect, scope) {
if (scope === void 0) { scope = activeEffectScope; }
if (scope && scope.active) {
scope.effects.push(effect);
}
}
function getCurrentScope() {
return activeEffectScope;
}
function onScopeDispose(fn) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn);
}
else {
warn$2("onScopeDispose() is called when there is no active effect scope" +
" to be associated with.");
}
}
var activeInstance = null; var activeInstance = null;
var isUpdatingChildComponent = false; var isUpdatingChildComponent = false;
function setActiveInstance(vm) { function setActiveInstance(vm) {
@@ -3211,8 +3095,7 @@
if (setContext === void 0) { setContext = true; } if (setContext === void 0) { setContext = true; }
// #7573 disable dep collection when invoking lifecycle hooks // #7573 disable dep collection when invoking lifecycle hooks
pushTarget(); pushTarget();
var prevInst = currentInstance; var prev = currentInstance;
var prevScope = getCurrentScope();
setContext && setCurrentInstance(vm); setContext && setCurrentInstance(vm);
var handlers = vm.$options[hook]; var handlers = vm.$options[hook];
var info = "".concat(hook, " hook"); var info = "".concat(hook, " hook");
@@ -3224,10 +3107,7 @@
if (vm._hasHookEvent) { if (vm._hasHookEvent) {
vm.$emit('hook:' + hook); vm.$emit('hook:' + hook);
} }
if (setContext) { setContext && setCurrentInstance(prev);
setCurrentInstance(prevInst);
prevScope && prevScope.on();
}
popTarget(); popTarget();
} }
@@ -3445,10 +3325,7 @@
var instance = currentInstance; var instance = currentInstance;
var call = function (fn, type, args) { var call = function (fn, type, args) {
if (args === void 0) { args = null; } if (args === void 0) { args = null; }
var res = invokeWithErrorHandling(fn, null, args, instance, type); return invokeWithErrorHandling(fn, null, args, instance, type);
if (deep && res && res.__ob__)
res.__ob__.dep.depend();
return res;
}; };
var getter; var getter;
var forceTrigger = false; var forceTrigger = false;
@@ -3473,7 +3350,6 @@
return s.value; return s.value;
} }
else if (isReactive(s)) { else if (isReactive(s)) {
s.__ob__.dep.depend();
return traverse(s); return traverse(s);
} }
else if (isFunction(s)) { else if (isFunction(s)) {
@@ -3617,6 +3493,112 @@
}; };
} }
var activeEffectScope;
var EffectScope = /** @class */ (function () {
function EffectScope(detached) {
if (detached === void 0) { detached = false; }
this.detached = detached;
/**
* @internal
*/
this.active = true;
/**
* @internal
*/
this.effects = [];
/**
* @internal
*/
this.cleanups = [];
this.parent = activeEffectScope;
if (!detached && activeEffectScope) {
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1;
}
}
EffectScope.prototype.run = function (fn) {
if (this.active) {
var currentEffectScope = activeEffectScope;
try {
activeEffectScope = this;
return fn();
}
finally {
activeEffectScope = currentEffectScope;
}
}
else {
warn$2("cannot run an inactive effect scope.");
}
};
/**
* This should only be called on non-detached scopes
* @internal
*/
EffectScope.prototype.on = function () {
activeEffectScope = this;
};
/**
* This should only be called on non-detached scopes
* @internal
*/
EffectScope.prototype.off = function () {
activeEffectScope = this.parent;
};
EffectScope.prototype.stop = function (fromParent) {
if (this.active) {
var i = void 0, l = void 0;
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].teardown();
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]();
}
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true);
}
}
// nested scope, dereference from parent to avoid memory leaks
if (!this.detached && this.parent && !fromParent) {
// optimized O(1) removal
var last = this.parent.scopes.pop();
if (last && last !== this) {
this.parent.scopes[this.index] = last;
last.index = this.index;
}
}
this.parent = undefined;
this.active = false;
}
};
return EffectScope;
}());
function effectScope(detached) {
return new EffectScope(detached);
}
/**
* @internal
*/
function recordEffectScope(effect, scope) {
if (scope === void 0) { scope = activeEffectScope; }
if (scope && scope.active) {
scope.effects.push(effect);
}
}
function getCurrentScope() {
return activeEffectScope;
}
function onScopeDispose(fn) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn);
}
else {
warn$2("onScopeDispose() is called when there is no active effect scope" +
" to be associated with.");
}
}
function provide(key, value) { function provide(key, value) {
if (!currentInstance) { if (!currentInstance) {
{ {
@@ -3911,7 +3893,7 @@
suspensible = _b === void 0 ? false : _b, // in Vue 3 default is true suspensible = _b === void 0 ? false : _b, // in Vue 3 default is true
userOnError = source.onError; userOnError = source.onError;
if (suspensible) { if (suspensible) {
warn$2("The suspensible option for async components is not supported in Vue2. It is ignored."); warn$2("The suspensiblbe option for async components is not supported in Vue2. It is ignored.");
} }
var pendingRequest = null; var pendingRequest = null;
var retries = 0; var retries = 0;
@@ -4014,7 +3996,7 @@
/** /**
* Note: also update dist/vue.runtime.mjs when adding new exports to this file. * Note: also update dist/vue.runtime.mjs when adding new exports to this file.
*/ */
var version = '2.7.16'; var version = '2.7.14';
/** /**
* @internal type is manually declared in <root>/types/v3-define-component.d.ts * @internal type is manually declared in <root>/types/v3-define-component.d.ts
*/ */
@@ -4391,7 +4373,7 @@
"Instead, use a data or computed property based on the prop's " + "Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"".concat(key, "\""), vm); "value. Prop being mutated: \"".concat(key, "\""), vm);
} }
}, true /* shallow */); });
} }
// static props are already proxied on the component's prototype // static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at // during Vue.extend(). We only need to proxy props defined at
@@ -4707,9 +4689,6 @@
vm.__v_skip = true; vm.__v_skip = true;
// effect scope // effect scope
vm._scope = new EffectScope(true /* detached */); vm._scope = new EffectScope(true /* detached */);
// #13134 edge case where a child component is manually created during the
// render of a parent component
vm._scope.parent = undefined;
vm._scope._vm = true; vm._scope._vm = true;
// merge options // merge options
if (options && options._isComponent) { if (options && options._isComponent) {
@@ -5956,7 +5935,7 @@
return false; return false;
} }
function pruneCache(keepAliveInstance, filter) { function pruneCache(keepAliveInstance, filter) {
var cache = keepAliveInstance.cache, keys = keepAliveInstance.keys, _vnode = keepAliveInstance._vnode, $vnode = keepAliveInstance.$vnode; var cache = keepAliveInstance.cache, keys = keepAliveInstance.keys, _vnode = keepAliveInstance._vnode;
for (var key in cache) { for (var key in cache) {
var entry = cache[key]; var entry = cache[key];
if (entry) { if (entry) {
@@ -5966,7 +5945,6 @@
} }
} }
} }
$vnode.componentOptions.children = undefined;
} }
function pruneCacheEntry(cache, key, keys, current) { function pruneCacheEntry(cache, key, keys, current) {
var entry = cache[key]; var entry = cache[key];
@@ -6288,7 +6266,7 @@
} }
var el = document.createElement(tag); var el = document.createElement(tag);
if (tag.indexOf('-') > -1) { if (tag.indexOf('-') > -1) {
// https://stackoverflow.com/a/28210364/1070244 // http://stackoverflow.com/a/28210364/1070244
return (unknownElementCache[tag] = return (unknownElementCache[tag] =
el.constructor === window.HTMLUnknownElement || el.constructor === window.HTMLUnknownElement ||
el.constructor === window.HTMLElement); el.constructor === window.HTMLElement);
@@ -7163,11 +7141,8 @@
var insert_1 = ancestor.data.hook.insert; var insert_1 = ancestor.data.hook.insert;
if (insert_1.merged) { if (insert_1.merged) {
// start at index 1 to avoid re-invoking component mounted hook // start at index 1 to avoid re-invoking component mounted hook
// clone insert hooks to avoid being mutated during iteration. for (var i_10 = 1; i_10 < insert_1.fns.length; i_10++) {
// e.g. for customed directives under transition group. insert_1.fns[i_10]();
var cloned = insert_1.fns.slice(1);
for (var i_10 = 0; i_10 < cloned.length; i_10++) {
cloned[i_10]();
} }
} }
} }
@@ -8306,8 +8281,10 @@
} }
for (name in newStyle) { for (name in newStyle) {
cur = newStyle[name]; cur = newStyle[name];
// ie9 setting to null has no effect, must use empty string if (cur !== oldStyle[name]) {
setProp(el, name, cur == null ? '' : cur); // ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur);
}
} }
} }
var style$1 = { var style$1 = {
@@ -9554,7 +9531,7 @@
return "continue"; return "continue";
} }
} }
// https://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) { if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf(']>'); var conditionalEnd = html.indexOf(']>');
if (conditionalEnd >= 0) { if (conditionalEnd >= 0) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -35,7 +35,7 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border: none; border: none;
border-bottom: var(--brand-bg) 2px solid; border-bottom: var(--text-color2) 2px solid;
background-color: var(--bg-color); background-color: var(--bg-color);
text-overflow: ellipsis; text-overflow: ellipsis;
min-height: 2em; min-height: 2em;
@@ -44,7 +44,7 @@
.form-row input:focus { .form-row input:focus {
outline: none; outline: none;
border: none; border: none;
border-bottom: var(--brand-text) 2px solid; border-bottom: var(--bg-action) 2px solid;
background-color: var(--bg-selected); background-color: var(--bg-selected);
} }
@@ -52,6 +52,7 @@
border: none; border: none;
font-weight: bolder; font-weight: bolder;
background: var(--bg-action); background: var(--bg-action);
color: var(--text-color);
text-align: center; text-align: center;
} }

File diff suppressed because it is too large Load Diff

1351
static/main-tdma.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,17 @@
body { body {
--text-color: #262626; --text-color: #262626;
--text-color2: #3d3d3d; --text-color2: #3d3d3d;
--text-good: green; --text-good: #0CF500;
--text-bad: red; --text-bad: #F5000C;
--brand-bg: #EDF3FE; --brand-bg: #B3C0D1;
--brand-text: #5488F7; --brand-text: #0146f4;
--bg-color: #FEFEFE; --bg-color: #FEFEFE;
--bg-selected: #F1F1F1; --bg-selected: #F1F1F1;
--bg-element: #a7a7a7; --bg-element: #a7a7a7;
--bg-action: #5181fe; --bg-action: #81a7ff;
--bg-danger: #db2828; --bg-danger: #ff6464;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -20,8 +20,8 @@ body {
body { body {
--text-color: #eee; --text-color: #eee;
--text-color2: #bbb; --text-color2: #bbb;
--text-good: greenyellow; --text-good: #91FF00;
--text-bad: orangered; --text-bad: #FF1F2A;
--brand-bg: #393E50; --brand-bg: #393E50;
--brand-text: #5F93F3; --brand-text: #5F93F3;
@@ -29,7 +29,8 @@ body {
--bg-color: #2d2c33; --bg-color: #2d2c33;
--bg-selected: #424248; --bg-selected: #424248;
--bg-element: #626268; --bg-element: #626268;
--bg-action: #4a70d5; --bg-action: #3a58af;
--bg-danger: #ac1e1e;
} }
} }
@@ -52,6 +53,13 @@ body {
margin: 0.5em; margin: 0.5em;
} }
/* увеличение размера шрифтов */
* { font-size: large; }
h1 { font-size: xxx-large; }
h2 { font-size: xx-large; }
h3 { font-size: larger; }
/* ========== MAIN STYLES ========== */ /* ========== MAIN STYLES ========== */
.value-good { .value-good {

Binary file not shown.