Compare commits

..

2 Commits

10 changed files with 407 additions and 90 deletions

View File

@ -34,10 +34,12 @@ add_executable(terminal-web-server
src/server/server.hpp src/server/server.hpp
src/server/resource.cpp src/server/resource.cpp
src/server/resource.h src/server/resource.h
src/terminal_api_driver.h
src/terminal_api_driver.cpp
) )
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}) target_link_libraries(terminal-web-server ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} terminal-client-api)
target_include_directories(terminal-web-server PRIVATE ${Boost_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) target_include_directories(terminal-web-server PRIVATE ${Boost_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})

View File

@ -28,6 +28,8 @@ add_library(terminal-client-api SHARED
client/system_client.cpp client/system_client.cpp
) )
target_include_directories(terminal-client-api PUBLIC "include/")
find_package(Boost 1.53.0 COMPONENTS system log log_setup REQUIRED) find_package(Boost 1.53.0 COMPONENTS system log log_setup REQUIRED)
find_package(cereal REQUIRED) find_package(cereal REQUIRED)
target_link_libraries(terminal-client-api ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} cereal::cereal) target_link_libraries(terminal-client-api ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} cereal::cereal)

View File

@ -1,5 +1,5 @@
#include <shared_mutex> #include <shared_mutex>
#include "ControlProtoCInterface.h" #include "terminal_api/ControlProtoCInterface.h"
#include "system_client.h" #include "system_client.h"
std::shared_mutex mtx; std::shared_mutex mtx;

View File

@ -1,13 +1,14 @@
#ifndef __CONTROL_PROTO_COMMANDS__ #ifndef __CONTROL_PROTO_COMMANDS__
#define __CONTROL_PROTO_COMMANDS__ #define __CONTROL_PROTO_COMMANDS__
#include <stdint.h>
#include <iostream> #include <iostream>
#include <string> #include <string>
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint>
#define EXTERNC extern "C" #define EXTERNC extern "C"
#else #else
#include <stdint.h>
#define EXTERNC extern #define EXTERNC extern
#endif #endif

View File

@ -15,6 +15,8 @@
#include <memory> #include <memory>
#include <fstream> #include <fstream>
#include "terminal_api_driver.h"
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
@ -70,9 +72,11 @@ void init_logging() {
log::add_common_attributes(); log::add_common_attributes();
} }
static void initResources(http::server::Server& s) { static void initResources(http::server::Server& s, std::shared_ptr<api_driver::ApiDriver>& api) {
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/login.html", mime_types::text_html)); s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/main.html", mime_types::text_html));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/login", "static/login.html", mime_types::text_html));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png)); s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/style.css", "static/style.css", mime_types::text_css));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript)); s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) { s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
@ -86,6 +90,20 @@ static void initResources(http::server::Server& s) {
const char* json = R"({"key":"value"})"; const char* json = R"({"key":"value"})";
rep.content.insert(rep.content.end(), json, json + strlen(json)); rep.content.insert(rep.content.end(), json, json + strlen(json));
})); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/mainStatistics", [api](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"({"mainState":)";
result += api->loadTerminalState();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -110,12 +128,14 @@ int main(int argc, char *argv[]) {
BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0]; BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0];
#endif #endif
auto api = std::make_shared<api_driver::ApiDriver>();
// Initialise the server. // Initialise the server.
std::unique_ptr<http::server::Server> s; std::unique_ptr<http::server::Server> s;
if (strcmp(argv[1], "nossl") == 0) { if (strcmp(argv[1], "nossl") == 0) {
s = std::make_unique<http::server::Server>(argv[2], argv[3]); s = std::make_unique<http::server::Server>(argv[2], argv[3]);
initResources(*s); initResources(*s, api);
s->run(); s->run();
} else if (strcmp(argv[1], "ssl") == 0) { } else if (strcmp(argv[1], "ssl") == 0) {
@ -131,15 +151,12 @@ int main(int argc, char *argv[]) {
}); });
ctx->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use); ctx->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
ctx->use_certificate_chain(boost::asio::buffer(cert)); ctx->use_certificate_chain(boost::asio::buffer(cert));
ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem); ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem);
ctx->use_tmp_dh(boost::asio::buffer(dh)); ctx->use_tmp_dh(boost::asio::buffer(dh));
s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx); s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
initResources(*s); initResources(*s, api);
s->run(); s->run();
} else { } else {
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl; std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;

View File

@ -0,0 +1,73 @@
#include "terminal_api_driver.h"
#include "terminal_api/ControlProtoCInterface.h"
#include <sstream>
api_driver::ApiDriver::ApiDriver() {
CP_Login("admin", "pass", &sid, &access);
}
static bool DriverCP_GetLevelDemod(TSID sid, const char* param) {
double variable_dbl = 0;
CP_GetLevelDemod(sid, param, &variable_dbl);
return variable_dbl == 0;
}
static bool DriverCP_GetGain(TSID sid, const char* param) {
double variable_dbl = 0;
CP_GetGain(sid, param, &variable_dbl);
return variable_dbl == 0;
}
static const char* boolAsStr(bool value) {
return value ? "true" : "false";
}
std::string api_driver::ApiDriver::loadTerminalState() {
std::stringstream result;
result << "{";
result << "\"txState\":" << boolAsStr(DriverCP_GetGain(sid, "TXPWD"));
const auto sym_sync_lock = DriverCP_GetLevelDemod(sid, "sym_sync_lock"); // захват символьной
const auto freq_search_lock = DriverCP_GetLevelDemod(sid, "freq_lock"); // Захват поиска по частоте
const auto afc_lock = DriverCP_GetLevelDemod(sid, "afc_lock"); // захват ФАПЧ
const auto pkt_sync = DriverCP_GetLevelDemod(sid, "pkt_sync"); // захват пакетной синхронизации
const auto receive_active = sym_sync_lock && freq_search_lock && afc_lock && pkt_sync;
result << ",\"rxState\":" << boolAsStr(receive_active);
result << ",\"rx.sym_sync_lock\":" << boolAsStr(sym_sync_lock);
result << ",\"rx.freq_search_lock\":" << boolAsStr(freq_search_lock);
result << ",\"rx.afc_lock\":" << boolAsStr(afc_lock);
result << ",\"rx.pkt_sync\":" << boolAsStr(pkt_sync);
result << "}";
// return R"({"rxState":0,"txState":0,"testState":0})";
return result.str();
}
std::string api_driver::ApiDriver::loadDeviceStatistics() {
std::stringstream result;
result << "{";
result << "\"txState\":" << boolAsStr(DriverCP_GetGain(sid, "TXPWD"));
const auto sym_sync_lock = DriverCP_GetLevelDemod(sid, "sym_sync_lock"); // захват символьной
const auto freq_search_lock = DriverCP_GetLevelDemod(sid, "freq_lock"); // Захват поиска по частоте
const auto afc_lock = DriverCP_GetLevelDemod(sid, "afc_lock"); // захват ФАПЧ
const auto pkt_sync = DriverCP_GetLevelDemod(sid, "pkt_sync"); // захват пакетной синхронизации
const auto receive_active = sym_sync_lock && freq_search_lock && afc_lock && pkt_sync;
result << ",\"rxState\":" << boolAsStr(receive_active);
result << ",\"rx.sym_sync_lock\":" << boolAsStr(sym_sync_lock);
result << ",\"rx.freq_search_lock\":" << boolAsStr(freq_search_lock);
result << ",\"rx.afc_lock\":" << boolAsStr(afc_lock);
result << ",\"rx.pkt_sync\":" << boolAsStr(pkt_sync);
result << "}";
// return R"({"rxState":0,"txState":0,"testState":0})";
return result.str();
}
api_driver::ApiDriver::~ApiDriver() = default;

37
src/terminal_api_driver.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef TERMINAL_API_DRIVER_H
#define TERMINAL_API_DRIVER_H
#include <string>
#include <terminal_api/ControlProtoCInterface.h>
namespace api_driver {
/**
* Это ApiDriver. Все ответы он будет возвращать в виде json.
*/
class ApiDriver {
public:
explicit ApiDriver();
/**
* Запросить общее состояние терминала
* @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();
/**
* Запросить статистику модулятора, демодулятора, CicC и температурные датчики
* @return
*/
std::string loadDeviceStatistics();
~ApiDriver();
private:
TSID sid{0};
unsigned int access{0};
};
}
#endif //TERMINAL_API_DRIVER_H

View File

@ -3,41 +3,101 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title> <title>Вход</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
#form-wrapper {
overflow: hidden;
max-width: 27em;
margin: 5em auto;
height: auto;
text-align: center;
}
.form-row {
padding: 4px 0;
margin: 1.5em;
}
.form-row * {
font-size: 1em;
text-align: left;
display: block;
}
.form-row label {
line-height: 2em;
font-weight: bolder;
}
.form-row input {
padding: 8px;
width: 100%;
box-sizing: border-box;
border: none;
border-bottom: var(--brand-bg) 2px solid;
background-color: var(--bg-color);
text-overflow: ellipsis;
min-height: 2em;
}
.form-row input:focus {
outline: none;
border: none;
border-bottom: var(--brand-text) 2px solid;
background-color: var(--bg-selected);
}
#submit {
border: none;
font-weight: bolder;
background: var(--bg-action);
text-align: center;
}
</style>
</head> </head>
<body> <body>
<header>
<div id="app">
<h1>{{ message }}</h1>
<p>{{ now }}</p>
</div>
<div id="status-header"></div> <div id="form-wrapper">
<h1> Вход </h1>
<form method="POST" id="login-form">
{% csrf_token %}
<!-- Версия для разработки включает в себя возможность вывода в консоль полезных уведомлений --> {% if message %}
<script src="/js/vue.js"></script> <div class="form-row value-bad">
<script> {{ message }}
const app = new Vue({ </div>
el: '#app', {% endif %}
data: {
message: 'Hello Vue!',
now: new Date()
},
methods: {
updateDate() {
this.now = new Date();
}
},
mounted() {
setInterval(() => {
this.updateDate();
}, 1000);
}
})
// import MyComponent from './modules/header' <div class="form-row">
// const sh = new Vue(MyComponent) <label for="username">Имя пользователя</label>
</script> <input type="text" name="username" id="username" required/>
</header> </div>
<div class="form-row">
<label for="password">Пароль</label>
<input type="password" name="password" id="password" required/>
</div>
<div class="form-row">
<input id="submit" type="submit" value="Войти">
</div>
</form>
</div>
<script>
document.getElementById("username").onkeydown = (e) => {
if (e.key === 'Enter') {
document.getElementById("password").focus()
}
}
document.getElementById("password").onkeydown = (e) => {
if (e.key === 'Enter') {
document.getElementById("login-form").submit()
}
}
</script>
</body> </body>
</html> </html>

118
static/main.html Normal file
View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Главная</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
.tabs-header {
margin: 0.5em 0;
background: var(--brand-bg);
}
.tabs-header > * {
display: inline-block;
}
.tabs-btn {
font-size: 18px;
border: none;
padding: 10px 25px;
text-align: center;
cursor: pointer;
margin-bottom: -3px;
border-bottom: 3px solid #eee;
}
.tabs-btn.active {
color: #5fa03a;
border-bottom: 3px solid #5fa03a;
}
.tabs-body-item {
padding: 20px 0;
}
</style>
</head>
<body>
<div id="app" hidden>
<div>
<h1>Общее состояние</h1>
<ul>
<li>Прием: {{ rxState }}</li>
<li>Передача: {{ txState }}</li>
<li>Тест: {{ testState }}</li>
<li>Последнее обновление: {{ lastUpdateTime }}</li>
</ul>
</div>
<div class="tabs">
<div class="tabs-header">
<span style="font-weight:bold">RSCM-101</span>
<button class="tabs-btn" @click="activeTab = 1" :class="{ active: activeTab === 1 }">Мониторинг</button>
<button class="tabs-btn" @click="activeTab = 2" :class="{ active: activeTab === 2 }">Прием/Передача</button>
<button class="tabs-btn" @click="activeTab = 3" :class="{ active: activeTab === 3 }">Настройки CinC</button>
<button class="tabs-btn" @click="activeTab = 4" :class="{ active: activeTab === 4 }">Настройки питания и опорного генератора</button>
<button class="tabs-btn" @click="activeTab = 5" :class="{ active: activeTab === 5 }">Администрирование</button>
</div>
<div class="tabs-body">
<div class="tabs-body-item" v-show="activeTab === 1">
<div>
<h2>Статистика передачи</h2>
</div>
</div>
<div class="tabs-body-item" v-show="activeTab === 2">
Содержимое вкладки 2
</div>
<div class="tabs-body-item" v-show="activeTab === 3">
Содержимое вкладки 3
</div>
<div class="tabs-body-item" v-show="activeTab === 4">
Содержимое вкладки 4
</div>
<div class="tabs-body-item" v-show="activeTab === 5">
Содержимое вкладки 5
</div>
</div>
</div>
</div>
<div id="mainState" hidden>
</div>
<div id="status-header"></div>
<!-- Версия для разработки включает в себя возможность вывода в консоль полезных уведомлений -->
<script src="/js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeTab: 1,
rxState: '?',
txState: '?',
testState: '?',
lastUpdateTime: new Date()
},
methods: {
updateMainState(vals) {
this.lastUpdateTime = new Date();
this.rxState = vals["mainState"]["rxState"]
this.txState = vals["mainState"]["txState"]
this.testState = vals["mainState"]["testState"]
}
},
mounted() {
setInterval(() => {
fetch("/api/mainStatistics").then(async (val) => {
this.updateMainState(await val.json())
});
}, 1000);
}
})
document.getElementById("app").removeAttribute("hidden")
// import MyComponent from './modules/header'
// const sh = new Vue(MyComponent)
</script>
</body>
</html>

View File

@ -1,75 +1,82 @@
:root { /* ========== THEME ========== */
--text: #000; body {
--bg: #fff; --text-color: #262626;
--text-color2: #3d3d3d;
--text-good: green;
--text-bad: red;
--item_bg: rgb(248, 246, 243); --brand-bg: #EDF3FE;
--second_text: #6e6d6d; --brand-text: #5488F7;
--bg-color: #FEFEFE;
--bg-selected: #F1F1F1;
--bg-action: #5181fe;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { /* defaults to dark theme */
--text: #fff; body {
--bg: #222226; --text-color: #eee;
--text-color2: #bbb;
--text-good: greenyellow;
--text-bad: orangered;
--item_bg: #333336; --brand-bg: #393E50;
--second_text: #828282; --brand-text: #5F93F3;
--bg-color: #2d2c33;
--bg-selected: #424248;
--bg-action: #4a70d5;
} }
} }
html { * {
padding: 10px; background: transparent;
color: var(--text-color);
}
*, *::before, *::after {
box-sizing: border-box;
} }
body { body {
display: flex; background: var(--bg-color);
flex-direction: column; margin: 0; /* браузеры зачем-то ставят свое значение */
font-family: 'Roboto', sans-serif;
font-size: 16px;
align-items: center;
justify-content: center;
background-color: var(--bg);
color: var(--text);
} }
a { #content {
text-decoration: none; margin: 0.5em;
background-color: var(--bg);
color: var(--text);
} }
.container { /* ========== MAIN STYLES ========== */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--bg);
color: var(--text);
}
.title { header > h1 {
text-align: center; text-align: center;
background-color: var(--brand-bg);
padding: 0.5em;
margin: 0;
} }
.item { header * {
padding: 20px; color: var(--brand-text);
width: 400px;
color: var(--text);
background-color: var(--item_bg);
border-bottom: 1px solid var(--text);
} }
.item_title { header > nav {
font-weight: 600; display: flex;
text-align: center; flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
} }
.item_status { header > nav > a {
text-align: end; margin: 0.5em;
font-weight: 300;
color: var(--second_text);
} }
.item_icon { .value-good {
width: 25px; color: var(--text-good);
}
.value-bad {
color: var(--text-bad);
} }