Compare commits

..

68 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
dd0a6813a8 добавил правила QoS по-умолчанию (пустой набор правил) 2024-11-12 11:39:09 +03:00
e2c9877017 фикс некорректного ответа QoS 2024-11-12 11:08:22 +03:00
b51e303006 фикс некорректного ответа QoS 2024-11-12 11:07:22 +03:00
00aa17bb37 добавил корректное сохранение конфига 2024-11-12 11:04:52 +03:00
5175362d1b сделал систему прав, теперь все действия с апи выполняются только при наличии прав (и в целом авторизации) 2024-11-12 10:56:26 +03:00
857a01528b добавил запись настроек BucLnb 2024-11-12 10:18:03 +03:00
cb9d412c8e куча изменений, но зато теперь сохраняются настройки QoS и есть алгоритм сохранения параметров в API 2024-11-11 17:35:25 +03:00
435f215118 сделал включение/отключение классов QoS и правил QoS 2024-11-11 10:45:01 +03:00
38 changed files with 6525 additions and 1499 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ cmake-build-*
cert.pem
key.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!")
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)
# максимальный размер тела запроса 200mb
@@ -47,6 +57,8 @@ add_executable(terminal-web-server
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(OpenSSL REQUIRED)
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(default_build_type "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -s -DNDEBUG ")
message(${CMAKE_CXX_FLAGS})
message("CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
message(${CMAKE_CXX_FLAGS})
add_library(terminal-client-api SHARED
add_library(terminal-client-api STATIC
client/main.cpp
client/sock_client.cpp
client/system_client.cpp

View File

@@ -6,6 +6,42 @@ std::shared_mutex mtx;
TSID sid_counter { 0 };
std::map<TSID, std::unique_ptr<system_client>> clients;
EXTERNC CP_Result CP_SetQoSSettings(TSID sid, const std::string &qos_settings_json, bool is_enable){
std::shared_lock lock(mtx);
try
{
if (clients.find(sid) == clients.end())
return ERROR;
auto resp = clients[sid]->send_set_qos_settings_json(qos_settings_json, is_enable);
if (resp == response_type::error)
return ERROR;
return OK;
}
catch(const std::exception& e)
{
return ERROR;
}
}
EXTERNC CP_Result CP_GetQoSSettings(TSID sid, std::string &qos_settings_json, bool &is_enable){
std::shared_lock lock(mtx);
try
{
if (clients.find(sid) == clients.end())
return ERROR;
auto resp = clients[sid]->send_get_qos_settings_json(qos_settings_json, is_enable);
if (resp == response_type::error)
return ERROR;
return OK;
}
catch(const std::exception& e)
{
return ERROR;
}
}
EXTERNC CP_Result CP_SetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings){
std::shared_lock lock(mtx);
@@ -25,6 +61,7 @@ EXTERNC CP_Result CP_SetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings){
return ERROR;
}
}
EXTERNC CP_Result CP_GetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings){
std::shared_lock lock(mtx);
try

View File

@@ -433,6 +433,16 @@ void system_client::data_received(const std::vector<uint8_t> & data)
}
break;
}
case cmd_type::get_qos_settings_json:
{
if (cmd.rsp == response_type::ok)
{
cmd_qos_settings value;
iarchive(value);
data_from_serv = value;
}
break;
}
default: break;
}
@@ -705,6 +715,40 @@ response_type system_client::send_get_qos_params(std::string &node, const name_c
return result;
}
response_type system_client::send_set_qos_settings_json(const std::string &json_string, bool is_enable){
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_qos_header{curr_id, cmd_type::set_qos_settings_json};
cmd_qos_settings qos_settings{json_string, is_enable};
send_to_socket(cmd_qos_header, qos_settings);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_qos_settings, data_to_serv);
return result;
}
response_type system_client::send_get_qos_settings_json(std::string &json_string, bool &is_enable){
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_qos_header{curr_id, cmd_type::get_qos_settings_json};
cmd_qos_settings qos_settings{json_string, is_enable};
send_to_socket(cmd_qos_header, qos_settings);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_qos_settings_json, data_from_serv);
if (data_from_serv.has_value())
{
json_string = std::any_cast<cmd_qos_settings>(data_from_serv).json_string;
is_enable = std::any_cast<cmd_qos_settings>(data_from_serv).is_enable;
}
return result;
}
response_type system_client::send_set_10g_config(const cmd_10g_config & _10g_config, std::string &value)
{
std::scoped_lock lock(cmd_in_progress_mtx);

View File

@@ -70,6 +70,8 @@ public:
response_type send_set_qos_params(const std::string &node, const name_classes_qos & class_qos);
response_type send_get_qos_params(std::string &node, const name_classes_qos & class_qos);
response_type send_set_qos_settings_json(const std::string &json_string, bool is_enable);
response_type send_get_qos_settings_json(std::string &json_string, bool &is_enable);
void set_stdout_callback(std::function<void(const char *, uint32_t)> cb);
void abort();

View File

@@ -57,6 +57,7 @@ struct modulator_settings_com{
bool is_carrier;
bool tx_is_on;
bool is_cinc;
uint32_t modcod_tx;
};
struct modulator_state_com{
@@ -390,7 +391,9 @@ enum class cmd_type
get_modulator_settings = 37,
get_demodulator_settings = 38,
set_lnb_buc_settings = 39,
get_lnb_buc_settings = 40
get_lnb_buc_settings = 40,
set_qos_settings_json = 41,
get_qos_settings_json = 42
};
struct cmd_lbq_params
@@ -474,7 +477,7 @@ struct cmd_modulator_settings
archive(modulator_settings.attenuation,modulator_settings.baudrate, modulator_settings.central_freq_in_kGz,
modulator_settings.is_carrier, modulator_settings.is_cinc,
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);
}
};
@@ -565,7 +568,15 @@ struct cmd_get_acm_param
archive(enable, max_attenuation, max_modcod, min_attenuation, min_modcod, snr_treashold, enable_auto_atten, snr_treashold_acm, period_pack);
}
};
struct cmd_qos_settings{
std::string json_string;
bool is_enable;
template<class Archive>
void serialize(Archive & archive)
{
archive(json_string, is_enable);
}
};
struct cmd_set_qos_settings
{
std::string node;

View File

@@ -1,6 +1,7 @@
#ifndef __CONTROL_PROTO_COMMANDS__
#define __CONTROL_PROTO_COMMANDS__
#include <stdint.h>
#include <iostream>
#include <string>
@@ -138,6 +139,7 @@ struct modulator_settings{
bool is_carrier;
bool tx_is_on;
bool is_cinc;
uint32_t modcod_tx;
};
EXTERNC CP_Result CP_SetModulatorSettings(TSID sid, modulator_settings& settings);
@@ -175,6 +177,8 @@ struct buc_lnb_settings
EXTERNC CP_Result CP_SetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings);
EXTERNC CP_Result CP_GetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings);
EXTERNC CP_Result CP_SetQoSSettings(TSID sid, const std::string &qos_settings_json, bool is_enable);
EXTERNC CP_Result CP_GetQoSSettings(TSID sid, std::string &qos_settings_json, bool &is_enable);
EXTERNC CP_Result CP_GetModulatorParams(TSID sid, const char *modulator_param, uint32_t *value);

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

@@ -1,6 +1,7 @@
#include "resources.h"
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string.hpp>
#include <utility>
#include "jwt.h"
#include "utils.h"
@@ -8,6 +9,9 @@
http::auth::User::User(const std::string &username, const std::string &passwordHash): username(username),
passwordHash(passwordHash.empty() ? utils::sha256(username) : passwordHash) {}
http::auth::User::User(const std::string &username, const std::string &passwordHash, uint32_t perms): perms(perms),
username(username), passwordHash(passwordHash.empty() ? utils::sha256(username) : passwordHash) {}
bool http::auth::User::checkPassword(const std::string &pass) const {
return utils::sha256(pass) == passwordHash;
}
@@ -19,9 +23,8 @@ void http::auth::User::setPassword(const std::string &pass) {
bool http::auth::User::checkPremisions(uint32_t p) const {
if (this->perms & SUPERUSER) {
return true;
} else {
return (this->perms & p) == p;
}
return (this->perms & p) == p;
}
void http::auth::User::setPremisions(uint32_t p) {
@@ -73,3 +76,23 @@ std::shared_ptr<http::auth::User> http::auth::AuthProvider::getSession(const ser
}
http::auth::AuthProvider::~AuthProvider() = default;
http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path, AuthProvider& provider, resource::respGenerator generator):
BasicResource(path), provider_(provider), generator_(std::move(generator)), perms(User::SUPERUSER) {}
http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path, AuthProvider& provider, uint32_t perms, resource::respGenerator generator):
BasicResource(path), provider_(provider), generator_(std::move(generator)), perms(perms) {}
void http::auth::AuthRequiredResource::handle(const server::Request &req, server::Reply &rep) {
if (auto user = this->provider_.getSession(req)) {
if (user->checkPremisions(this->perms)) {
this->generator_(req, rep);
return;
}
stockReply(server::forbidden, rep);
} else {
stockReply(server::unauthorized, rep);
}
}
http::auth::AuthRequiredResource::~AuthRequiredResource() = default;

View File

@@ -16,12 +16,20 @@ namespace http::auth {
std::string passwordHash;
/**
* Конструктор пользователя.
* Конструктор пользователя. По-умолчанию пользователь создается без прав, их нужно задать отдельной функцией.
* @param username Имя пользователя, он же - логин.
* @param passwordHash Хеш sha256 пароля пользователя. Если передать пустой, то пароль будет сгенерирован такой же, как и имя пользователя.
*/
explicit User(const std::string& username, const std::string& passwordHash = "");
/**
* Конструктор пользователя с указанными правами. Аналогично первому конструктору.
* @param username Имя пользователя, он же - логин.
* @param passwordHash Хеш sha256 пароля пользователя. Если передать пустой, то пароль будет сгенерирован такой же, как и имя пользователя.
* @param perms Права пользователя
*/
explicit User(const std::string& username, const std::string& passwordHash, uint32_t perms);
/**
* Проверить пароль на соответствие хешу
* @param pass
@@ -42,6 +50,7 @@ namespace http::auth {
static constexpr uint32_t WATCH_SETTINGS = 0x0008; // просмотр настроек , если недоступно, то вкладки с настройками не будет
static constexpr uint32_t EDIT_SETTINGS = 0x0010; // редактирование настроек, установка параметров модулятора/демодулятора/dma/cinc
static constexpr uint32_t UPDATE_FIRMWARE = 0x0020; // обновление прошивки
static constexpr uint32_t SETUP_QOS = 0x0040; // управление профилем QoS
/**
* Проверить, что у пользователя есть нужное право. Если это суперпользователь, то у него по умолчанию все права есть.
@@ -79,17 +88,19 @@ namespace http::auth {
~AuthProvider();
};
class AuentificationRequiredResource final: public resource::BasicResource {
class AuthRequiredResource final: public resource::BasicResource {
public:
explicit AuentificationRequiredResource(const std::string& path, std::shared_ptr<AuthProvider> provider, resource::respGenerator generator);
explicit AuthRequiredResource(const std::string& path, AuthProvider& provider, resource::respGenerator generator);
explicit AuthRequiredResource(const std::string& path, AuthProvider& provider, uint32_t perms, resource::respGenerator generator);
void handle(const server::Request &req, server::Reply &rep) override;
~AuentificationRequiredResource() override;
~AuthRequiredResource() override;
private:
AuthProvider& provider_;
resource::respGenerator generator_;
std::shared_ptr<AuthProvider> provider_;
uint32_t perms;
};
}

View File

@@ -9,9 +9,12 @@
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];
SHA256(reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length(), hash);
SHA256(reinterpret_cast<const unsigned char *>(data), size, hash);
// Преобразуем хеш в шестнадцатеричную строку
std::stringstream ss;

View File

@@ -6,6 +6,7 @@
namespace http::utils {
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 b64Encode(const char* data, size_t size);

View File

@@ -11,7 +11,6 @@
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <cstddef>
#include <memory>
@@ -20,10 +19,14 @@
#include "terminal_api_driver.h"
#include "auth/resources.h"
#include "auth/jwt.h"
#include "auth/utils.h"
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) {
std::ifstream is(path, std::ios::in | std::ios::binary);
@@ -81,42 +84,59 @@ class ServerResources {
std::unique_ptr<api_driver::ApiDriver> api;
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:
static constexpr const char* INDEX_HTML = "static/main.html";
static constexpr const char* LOGIN_HTML = "static/login.html";
#if defined(MODEM_IS_TDMA)
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* KROKODIL_GIF = "static/krokodil.gif";
static constexpr const char* VUE_JS = "static/js/vue.js"; // это тоже можно кешировать
static constexpr const char* FAVICON_ICO = "/favicon.ico";
static constexpr const char* VUE_JS = "/js/vue.js"; // это тоже можно кешировать
// а эти стили нельзя кешировать в отладочной версии
static constexpr const char* STYLE_CSS = "static/style.css";
static constexpr const char* FIELDS_CSS = "static/fields.css";
static constexpr const char* KB_MP4 = "static/video_2024-11-06_15-49-35.mp4";
static constexpr const char* STYLE_CSS = "/style.css";
static constexpr const char* FIELDS_CSS = "/fields.css";
static constexpr const char* INTERNET_JPG = "/internet.jpg";
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();
auth.users.emplace_back(std::make_shared<http::auth::User>("admin"));
auth.users.emplace_back(std::make_shared<http::auth::User>("admin", "", http::auth::User::SUPERUSER));
sf->registerFile(INDEX_HTML, mime_types::text_html, false);
sf->registerFile(LOGIN_HTML, mime_types::text_html, false);
sf->registerFile(FAVICON_ICO, mime_types::image_png, true);
sf->registerFile(KROKODIL_GIF, mime_types::image_gif, true);
sf->registerFile(VUE_JS, mime_types::javascript, true);
sf->registerFile(KB_MP4, mime_types::video_mp4, true);
#if USE_DEBUG
constexpr bool allowCacheCss = false;
#else
constexpr bool allowCacheCss = true;
#endif
sf->registerFile(STYLE_CSS, mime_types::text_css, allowCacheCss);
sf->registerFile(FIELDS_CSS, mime_types::text_css, allowCacheCss);
sf->registerFile(staticFilesPath + "/favicon.png", FAVICON_ICO, mime_types::image_png, true);
sf->registerFile(staticFilesPath + VUE_JS, VUE_JS, mime_types::javascript, true);
sf->registerFile(staticFilesPath + STYLE_CSS, STYLE_CSS, mime_types::text_css, 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);
sf->registerFile(staticFilesPath + LOGIN_HTML, LOGIN_HTML, mime_types::text_html, true);
sf->registerFile(staticFilesPath + INTERNET_JPG, INTERNET_JPG, mime_types::image_jpeg, true);
}
void registerResources(http::server::Server& s) {
@@ -161,7 +181,7 @@ public:
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") {
http::server::httpRedirect(rep, "/login");
rep.headers.push_back({.name = "Set-Cookie", .value = http::auth::jwt::EMPTY_AUTH_COOKIE});
@@ -170,26 +190,13 @@ 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>("/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>("/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>("/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>("/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::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>(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>(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>("/api/statistics", [](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 char* json = R"({"key":"value"})";
rep.content.insert(rep.content.end(), json, json + strlen(json));
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/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") {
http::server::stockReply(http::server::bad_request, rep);
}
@@ -199,11 +206,39 @@ public:
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"mainState":)";
result += api->loadTerminalState();
result += R"(,"sysinfo":)";
result += api->loadSysInfo();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/resetPacketStatistics", [this](const auto& req, auto& rep) {
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/settings", this->auth, http::auth::User::WATCH_SETTINGS, [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)});
std::string result = R"({"settings":)";
result += api->loadSettings();
result += "}";
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) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
}
@@ -216,18 +251,242 @@ public:
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/settings", [this](const auto& req, auto& rep) {
if (req.method != "GET") {
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/qos", this->auth, http::auth::User::SETUP_QOS, [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)});
std::string result = R"({"settings":)";
result += api->loadSettings();
result += "}";
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setQosSettings(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/qos): Can't set QoS 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/bucLnb", 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)});
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setBucLnbSettings(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/bucLnb): Can't set settings: " << e.what();
const std::string result = R"({"status":"error"})";
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 += "\"}";
}));
}
@@ -242,9 +501,9 @@ int main(int argc, char *argv[]) {
if (argc != 4 && argc != 5) {
std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\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 << " receiver nossl 0::0 80\n";
std::cerr << " receiver nossl 0::0 80 .\n";
return 1;
}
@@ -265,7 +524,9 @@ int main(int argc, char *argv[]) {
BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey;
#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.
std::unique_ptr<http::server::Server> s;

View File

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

View File

@@ -72,6 +72,8 @@ namespace http::server {
/// The reply to be sent back to the client.
Reply reply_;
bool needClose = false;
};
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.
Reply reply_;
bool needClose = false;
};
typedef std::shared_ptr<ConnectionBase> connection_ptr;

View File

@@ -1,11 +1,30 @@
#include "request_parser.hpp"
#include <sstream>
#include "request.hpp"
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) {
std::istringstream iss(query);
std::string param;
@@ -72,7 +91,7 @@ namespace http::server {
switch (state_) {
case expecting_payload:
req.payload.push_back(input);
if (req.payload.size() <= contentLenghtHeader - 1) {
if (req.payload.size() < contentLenghtHeader) {
return indeterminate;
}
return good;
@@ -186,17 +205,23 @@ namespace http::server {
if (input == '\r') {
state_ = expecting_newline_3;
return indeterminate;
} else if (!req.headers.empty() && (input == ' ' || input == '\t')) {
}
if (!req.headers.empty() && (input == ' ' || input == '\t')) {
state_ = header_lws;
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:
if (input == '\r') {
state_ = expecting_newline_2;
@@ -251,12 +276,14 @@ namespace http::server {
if (content_len.empty()) {
return good;
}
contentLenghtHeader = std::stol(content_len);
state_ = expecting_payload;
if (contentLenghtHeader > HTTP_MAX_PAYLOAD) {
return bad;
contentLenghtHeader = std::stoul(content_len);
if (contentLenghtHeader == 0) {
return good;
}
if (requestBodySizeResolver(req, contentLenghtHeader)) {
state_ = expecting_payload;
return indeterminate;
}
return indeterminate;
}
return bad;

View File

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

View File

@@ -3,8 +3,6 @@
#include <fstream>
#include <utility>
#include "../../dependencies/control_system/common/protocol_commands.h"
static void loadFile(const std::string& path, std::vector<char>& content) {
std::ifstream is(path, std::ios::in | std::ios::binary);
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::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) {
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path;
loadFile(this->path, this->content);
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->webPath;
loadFile(path, this->content);
} 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::StaticFileFactory() = default;
void http::resource::StaticFileFactory::registerFile(const std::string &path, server::mime_types::Mime type, bool allowCache) {
this->files.emplace_back(path, type, 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, webPath, type, allowCache);
}
void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) {
for (auto& f: this->files) {
if (f.path == path) {
if (f.webPath == path) {
#ifdef USE_DEBUG
if (f.allowCache) {
rep.content.clear();
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
} else {
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.headers.clear();
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 StaticFileDef {
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;
bool allowCache;
std::vector<char> content;
@@ -36,7 +39,7 @@ namespace http::resource {
public:
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);

File diff suppressed because it is too large Load Diff

View File

@@ -3,51 +3,79 @@
#include <memory>
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <terminal_api/ControlProtoCInterface.h>
namespace api_driver {
constexpr int CACHE_STATISTICS_UPDATE_MS = 500;
constexpr int CACHE_SETTINGS_UPDATE_MS = 5000;
constexpr int CACHE_QOS_UPDATE_MS = 5000;
class TerminalApiDaemon;
/**
* Это ApiDriver. Все ответы он будет возвращать в виде json.
*/
* Это ApiDriver. Все ответы он будет возвращать в виде json.
*/
class ApiDriver {
public:
explicit ApiDriver();
/**
* Запуск демона
*/
* Запуск демона
*/
void startDaemon();
/**
* Запросить общее состояние терминала
* @return {"txState":false,"rxState":false,"rx.sym_sync_lock":false,"rx.freq_search_lock":false,"rx.afc_lock":false,"rx.pkt_sync":false}
*/
* Запросить общее состояние терминала
* @return {"txState":false,"rxState":false,"rx.sym_sync_lock":false,"rx.freq_search_lock":false,"rx.afc_lock":false,"rx.pkt_sync":false}
*/
std::string loadTerminalState() const;
/**
* Сбросить статистику пакетов
*/
* Сбросить статистику пакетов
*/
void resetPacketStatistics() const;
std::string loadSettings() const;
std::string loadFirmwareVersion() const;
/**
* Установить настройки RX/TX, readback можно получить используя loadTerminalState
*/
void setRxTxSettings(boost::property_tree::ptree &pt);
#ifdef MODEM_IS_SCPC
/**
* Установить настройки CinC, readback можно получить используя loadTerminalState.
*/
void setCincSettings(boost::property_tree::ptree &pt);
#endif
/**
* Установить настройки BUC и LNB, readback можно получить используя loadTerminalState.
*/
void setBucLnbSettings(boost::property_tree::ptree &pt);
/**
* Установить настройки QoS, readback можно получить используя loadTerminalState.
*/
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();
private:
TSID sid{0};
unsigned int access{0};
std::string deviceInitState;
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 {
margin: 0.5em 0;
margin: 0.5em 0 3px;
background: var(--brand-bg);
}
.tabs-header > * {
@@ -7,7 +7,6 @@
}
.tabs-btn {
text-decoration: none;
font-size: 18px;
border: none;
padding: 10px 25px;
text-align: center;
@@ -17,7 +16,7 @@
}
.tabs-btn.active {
color: var(--brand-text);
border-bottom: 3px solid var(--brand-text);
border-bottom: 3px solid var(--bg-action);
}
.tabs-body-item {
padding: 20px 0;
@@ -37,6 +36,30 @@
border-radius: 0.5em;
}
.dangerous-button, .action-button {
border: solid 1px var(--text-color2);
border-radius: 0.5em;
padding: 0.3em;
margin: 0.2em;
}
.summary-actions {
display: flex;
flex-direction: row;
align-items: stretch;
}
.summary-actions * {
margin: 0;
}
.dangerous-button {
background: var(--bg-danger);
}
.action-button {
background: var(--bg-action);
}
.nav-bar-element {
margin: 0.5em;
border-bottom: 2px solid var(--text-color2);
@@ -61,11 +84,12 @@
padding: 1em;
}
.tabs-item-flex-container th {
.settings-set-container th {
text-align: left;
font-weight: normal;
padding-right: 1em;
}
.tabs-item-flex-container td {
.settings-set-container td {
min-width: 10em;
}
.tabs-item-flex-container h2 {
@@ -76,11 +100,12 @@
margin: 0;
}
.settings-set-container label * {
label * {
display: block;
}
.settings-set-container label {
margin: 1em 0;
label {
margin: 1em 0.5em;
display: block;
/*background: var(--bg-selected);*/
color: var(--text-color2);
@@ -88,7 +113,7 @@
.settings-set-container input, .settings-set-container select {
margin-top: 0.5em;
border: none;
border-bottom: solid 2px var(--text-color);
border-bottom: solid 2px var(--text-color2);
width: 20em;
box-sizing: border-box;
}
@@ -96,7 +121,11 @@
.settings-set-container input:focus {
outline: 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 */
@@ -113,27 +142,10 @@ select * {
background: var(--bg-selected);
}
.settings-set-container details summary {
details > summary {
display: flex;
justify-content: space-between;
}
.dangerous-button, .action-button {
border: solid 1px var(--text-color2);
border-radius: 0.5em;
padding: 0.3em;
margin: 0.2em;
}
summary .dangerous-button, summary .action-button {
margin: 0;
}
.dangerous-button {
background: var(--bg-danger);
}
.action-button {
background: var(--brand-bg);
align-items: stretch;
}
.submit-spinner {
@@ -154,7 +166,6 @@ summary .dangerous-button, summary .action-button {
/*********************** Стили для красивых 'switch' ***********************/
.toggle-input {
display: inline-block;
position: relative;
margin: 5px 10px;
width: 50px;
@@ -182,7 +193,7 @@ summary .dangerous-button, summary .action-button {
.toggle-input input[type="checkbox"]:checked + .slider {
left: 25px;
background-color: var(--brand-text);
background-color: var(--bg-action);
}
.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
* (c) 2014-2023 Evan You
* Vue.js v2.7.14
* (c) 2014-2022 Evan You
* Released under the MIT License.
*/
(function (global, factory) {
@@ -82,16 +82,9 @@
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, replacer, 2)
? JSON.stringify(val, null, 2)
: 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.
* If the conversion fails, return original string.
@@ -253,7 +246,9 @@
*/
function genStaticKeys$1(modules) {
return modules
.reduce(function (keys, m) { return keys.concat(m.staticKeys || []); }, [])
.reduce(function (keys, m) {
return keys.concat(m.staticKeys || []);
}, [])
.join(',');
}
/**
@@ -756,11 +751,6 @@
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 pendingCleanupDeps = [];
var cleanupDeps = function () {
@@ -892,7 +882,7 @@
});
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
* update computation.
@@ -951,7 +941,7 @@
var keys = Object.keys(value);
for (var i = 0; i < keys.length; 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.
*/
function defineReactive(obj, key, val, customSetter, shallow, mock, observeEvenIfShallow) {
if (observeEvenIfShallow === void 0) { observeEvenIfShallow = false; }
function defineReactive(obj, key, val, customSetter, shallow, mock) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
@@ -999,10 +988,10 @@
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)) {
(val === NO_INIITIAL_VALUE || arguments.length === 2)) {
val = obj[key];
}
var childOb = shallow ? val && val.__ob__ : observe(val, false, mock);
var childOb = !shallow && observe(val, false, mock);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
@@ -1047,7 +1036,7 @@
else {
val = newVal;
}
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock);
childOb = !shallow && observe(newVal, false, mock);
{
dep.notify({
type: "set" /* TriggerOpTypes.SET */,
@@ -2510,10 +2499,11 @@
// to the data on the placeholder node.
vm.$vnode = _parentVnode;
// render self
var prevInst = currentInstance;
var prevRenderInst = currentRenderingInstance;
var vnode;
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);
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement);
@@ -2537,8 +2527,8 @@
}
}
finally {
currentRenderingInstance = prevRenderInst;
setCurrentInstance(prevInst);
currentRenderingInstance = null;
setCurrentInstance();
}
// if the returned array contains only a single node, allow it
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 isUpdatingChildComponent = false;
function setActiveInstance(vm) {
@@ -3211,8 +3095,7 @@
if (setContext === void 0) { setContext = true; }
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget();
var prevInst = currentInstance;
var prevScope = getCurrentScope();
var prev = currentInstance;
setContext && setCurrentInstance(vm);
var handlers = vm.$options[hook];
var info = "".concat(hook, " hook");
@@ -3224,10 +3107,7 @@
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
if (setContext) {
setCurrentInstance(prevInst);
prevScope && prevScope.on();
}
setContext && setCurrentInstance(prev);
popTarget();
}
@@ -3445,10 +3325,7 @@
var instance = currentInstance;
var call = function (fn, type, args) {
if (args === void 0) { args = null; }
var res = invokeWithErrorHandling(fn, null, args, instance, type);
if (deep && res && res.__ob__)
res.__ob__.dep.depend();
return res;
return invokeWithErrorHandling(fn, null, args, instance, type);
};
var getter;
var forceTrigger = false;
@@ -3473,7 +3350,6 @@
return s.value;
}
else if (isReactive(s)) {
s.__ob__.dep.depend();
return traverse(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) {
if (!currentInstance) {
{
@@ -3911,7 +3893,7 @@
suspensible = _b === void 0 ? false : _b, // in Vue 3 default is true
userOnError = source.onError;
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 retries = 0;
@@ -4014,7 +3996,7 @@
/**
* 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
*/
@@ -4391,7 +4373,7 @@
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"".concat(key, "\""), vm);
}
}, true /* shallow */);
});
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
@@ -4707,9 +4689,6 @@
vm.__v_skip = true;
// effect scope
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;
// merge options
if (options && options._isComponent) {
@@ -5956,7 +5935,7 @@
return false;
}
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) {
var entry = cache[key];
if (entry) {
@@ -5966,7 +5945,6 @@
}
}
}
$vnode.componentOptions.children = undefined;
}
function pruneCacheEntry(cache, key, keys, current) {
var entry = cache[key];
@@ -6288,7 +6266,7 @@
}
var el = document.createElement(tag);
if (tag.indexOf('-') > -1) {
// https://stackoverflow.com/a/28210364/1070244
// http://stackoverflow.com/a/28210364/1070244
return (unknownElementCache[tag] =
el.constructor === window.HTMLUnknownElement ||
el.constructor === window.HTMLElement);
@@ -7163,11 +7141,8 @@
var insert_1 = ancestor.data.hook.insert;
if (insert_1.merged) {
// start at index 1 to avoid re-invoking component mounted hook
// clone insert hooks to avoid being mutated during iteration.
// e.g. for customed directives under transition group.
var cloned = insert_1.fns.slice(1);
for (var i_10 = 0; i_10 < cloned.length; i_10++) {
cloned[i_10]();
for (var i_10 = 1; i_10 < insert_1.fns.length; i_10++) {
insert_1.fns[i_10]();
}
}
}
@@ -8306,8 +8281,10 @@
}
for (name in newStyle) {
cur = newStyle[name];
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur);
if (cur !== oldStyle[name]) {
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur);
}
}
}
var style$1 = {
@@ -9554,7 +9531,7 @@
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)) {
var conditionalEnd = html.indexOf(']>');
if (conditionalEnd >= 0) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

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

1700
static/main-scpc.html Normal file

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

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.