This commit is contained in:
parent
6803f84677
commit
8229aab949
@ -10,7 +10,7 @@
|
||||
#include "UdpServer.h"
|
||||
#include "WeChatContext/CorporationContext.h"
|
||||
#include "WeChatContext/WeChatContext.h"
|
||||
#include "WebApplication.h"
|
||||
#include "WebApplication/Application.h"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
@ -97,7 +97,7 @@ int main(int argc, char const *argv[]) {
|
||||
|
||||
auto udpServer = std::make_shared<UdpServer>(application->ioContext());
|
||||
auto mediaServer = std::make_shared<MediaServer>(554, false);
|
||||
auto webApp = Singleton<WebApplication>::instance<Construct>(application->getWtPort(), application->getDocumentRoot());
|
||||
auto webApp = Singleton<WebToolkit::Server>::instance<Construct>(application->getWtPort(), application->getDocumentRoot());
|
||||
|
||||
using namespace boost::asio::ip;
|
||||
auto proxyAddress = make_address(application->getServer());
|
||||
|
170
WebApplication/Application.cpp
Normal file
170
WebApplication/Application.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
#include "Application.h"
|
||||
#include "BoostLog.h"
|
||||
#include "BulmaTheme.h"
|
||||
#include "Database/Session.h"
|
||||
#include "Dialog.h"
|
||||
#include "LoginPage.h"
|
||||
#include "Restful.h"
|
||||
#include "VisitorRecordsPage.h"
|
||||
#include <Wt/Auth/AuthService.h>
|
||||
#include <Wt/Auth/HashFunction.h>
|
||||
#include <Wt/Auth/Identity.h>
|
||||
#include <Wt/Auth/PasswordService.h>
|
||||
#include <Wt/Auth/PasswordStrengthValidator.h>
|
||||
#include <Wt/Auth/PasswordVerifier.h>
|
||||
#include <Wt/Dbo/FixedSqlConnectionPool.h>
|
||||
#include <Wt/Dbo/SqlConnectionPool.h>
|
||||
#include <Wt/Dbo/backend/Sqlite3.h>
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <Wt/WEnvironment.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WServer.h>
|
||||
#include <format>
|
||||
|
||||
namespace WebToolkit {
|
||||
Application::Application(const Wt::WEnvironment &env, bool embedded) : Wt::WApplication(env) {
|
||||
messageResourceBundle().use(appRoot() + "wt");
|
||||
messageResourceBundle().use(appRoot() + "auth_strings");
|
||||
messageResourceBundle().use(appRoot() + "auth_css_theme");
|
||||
useStyleSheet("/resources/app.css");
|
||||
LOG(info) << "app root: " << appRoot();
|
||||
m_session = Database::session();
|
||||
m_session->login().changed().connect(this, &Application::authEvent);
|
||||
setTheme(std::make_shared<BulmaTheme>("bulma", !embedded));
|
||||
if (!embedded) {
|
||||
// setTheme(std::make_shared<Wt::WBootstrap2Theme>());
|
||||
m_root = root();
|
||||
} else {
|
||||
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
|
||||
m_root = topPtr.get();
|
||||
const std::string *div = env.getParameter("div");
|
||||
if (div) {
|
||||
setJavaScriptClass(*div);
|
||||
bindWidget(std::move(topPtr), *div);
|
||||
} else {
|
||||
LOG(error) << "Missing: parameter: 'div'";
|
||||
}
|
||||
auto externalPath = env.getParameter("path");
|
||||
if (externalPath != nullptr) {
|
||||
m_externalPath = *externalPath;
|
||||
LOG(info) << "external path: " << m_externalPath;
|
||||
} else {
|
||||
auto parameters = env.getParameterMap();
|
||||
for (auto &p : parameters) {
|
||||
LOG(info) << p.first;
|
||||
}
|
||||
}
|
||||
LOG(info) << "url: " << url();
|
||||
LOG(info) << "relative resources url: " << relativeResourcesUrl();
|
||||
LOG(info) << "resources url: " << resourcesUrl();
|
||||
}
|
||||
|
||||
if (!embedded) {
|
||||
root()->addWidget(std::make_unique<Wt::WText>(
|
||||
"Note: you can also run this application from within <a href=\"hello.html\">a web page</a>."));
|
||||
}
|
||||
LOG(info) << "internal path: " << internalPath();
|
||||
|
||||
m_root->addWidget(std::make_unique<Wt::WText>("Your name, please ? "));
|
||||
m_nameEdit = m_root->addWidget(std::make_unique<Wt::WLineEdit>());
|
||||
m_nameEdit->setFocus();
|
||||
|
||||
auto b = m_root->addWidget(std::make_unique<Wt::WPushButton>("点击我!"));
|
||||
b->setMargin(5, Wt::Side::Left);
|
||||
|
||||
m_root->addWidget(std::make_unique<Wt::WBreak>());
|
||||
|
||||
m_greeting = m_root->addWidget(std::make_unique<Wt::WText>());
|
||||
|
||||
b->clicked().connect(this, &Application::greet);
|
||||
m_nameEdit->enterPressed().connect(this, &Application::greet);
|
||||
|
||||
auto app = Amass::Singleton<Server>::instance();
|
||||
|
||||
m_root->addWidget(std::make_unique<Dialog>());
|
||||
internalPathChanged().connect(this, &Application::handlePathChange);
|
||||
handlePathChange(m_externalPath.empty() ? internalPath() : m_externalPath);
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
}
|
||||
|
||||
void Application::greet() {
|
||||
m_greeting->setText("Hello there, " + m_nameEdit->text());
|
||||
setInternalPath(m_externalPath);
|
||||
}
|
||||
|
||||
void Application::authEvent() {
|
||||
if (m_session->login().loggedIn()) {
|
||||
const Wt::Auth::User &u = m_session->login().user();
|
||||
LOG(info) << "User " << u.id() << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")"
|
||||
<< " logged in.";
|
||||
} else {
|
||||
LOG(info) << "User logged out.";
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handlePathChange(const std::string &path) {
|
||||
LOG(info) << "handlePathChange: " << path;
|
||||
if (path.starts_with("/wt/login") || path.starts_with("/wt/register")) {
|
||||
m_root->clear();
|
||||
m_root->setStyleClass("WtCenterContainer");
|
||||
m_root->addNew<LoginPage>(m_session->users(), m_session->login());
|
||||
} else if (path.starts_with("/wt/visitor/analysis")) {
|
||||
m_root->clear();
|
||||
m_root->addNew<VisitorRecordsPage>(*m_session);
|
||||
}
|
||||
}
|
||||
|
||||
Server::Server(uint16_t port, const std::string &documentRoot) {
|
||||
try {
|
||||
std::vector<std::string> args;
|
||||
args.push_back(std::format("--docroot={};/resources", documentRoot));
|
||||
args.push_back(std::format("--approot={}/resources", documentRoot));
|
||||
args.push_back(std::format("--http-listen=127.0.0.1:{}", port));
|
||||
initializeAuthenticationService();
|
||||
|
||||
m_server = std::make_unique<Wt::WServer>(std::format("{}/resources", documentRoot), args);
|
||||
|
||||
m_server->addEntryPoint(Wt::EntryPointType::Application,
|
||||
std::bind(&Server::createApplication, this, std::placeholders::_1, false));
|
||||
m_server->addEntryPoint(Wt::EntryPointType::WidgetSet,
|
||||
std::bind(&Server::createApplication, this, std::placeholders::_1, true), "/wt/app.js");
|
||||
m_server->addResource(std::make_shared<AuthenticationResource>(), "/auth");
|
||||
m_server->addResource(std::make_shared<PlaintextResource>(), "/plaintext");
|
||||
m_server->addResource(std::make_shared<DbResource>(std::format("{}/database.sqlite", documentRoot)), "/db");
|
||||
|
||||
m_server->start();
|
||||
} catch (const std::exception &e) {
|
||||
LOG(error) << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WApplication> Server::createApplication(const Wt::WEnvironment &env, bool embedded) {
|
||||
return std::make_unique<Application>(env, embedded);
|
||||
}
|
||||
|
||||
Server::~Server() {
|
||||
}
|
||||
|
||||
void Server::initializeAuthenticationService() {
|
||||
m_authService = std::make_unique<Wt::Auth::AuthService>();
|
||||
m_authService->setAuthTokensEnabled(true, "logincookie");
|
||||
m_passwordService = std::make_unique<Wt::Auth::PasswordService>(*m_authService);
|
||||
|
||||
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
|
||||
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
|
||||
m_passwordService->setVerifier(std::move(verifier));
|
||||
m_passwordService->setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
|
||||
m_passwordService->setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
|
||||
}
|
||||
|
||||
const Wt::Auth::AuthService &Server::authService() {
|
||||
return *m_authService;
|
||||
}
|
||||
|
||||
const Wt::Auth::PasswordService &Server::passwordService() {
|
||||
return *m_passwordService;
|
||||
}
|
||||
} // namespace WebToolkit
|
@ -2,6 +2,7 @@
|
||||
#define __WEBAPPLICATION_H__
|
||||
|
||||
#include "Singleton.h"
|
||||
#include <Wt/WApplication.h>
|
||||
#include <memory>
|
||||
|
||||
namespace Wt {
|
||||
@ -16,18 +17,41 @@ class PasswordService;
|
||||
|
||||
}; // namespace Wt
|
||||
|
||||
class WebApplication {
|
||||
friend class Amass::Singleton<WebApplication>;
|
||||
class Session;
|
||||
|
||||
namespace WebToolkit {
|
||||
|
||||
class Application : public Wt::WApplication {
|
||||
public:
|
||||
Application(const Wt::WEnvironment &env, bool embedded);
|
||||
~Application();
|
||||
|
||||
protected:
|
||||
void greet();
|
||||
void authEvent();
|
||||
void handlePathChange(const std::string &path);
|
||||
|
||||
private:
|
||||
Wt::WLineEdit *m_nameEdit = nullptr;
|
||||
Wt::WText *m_greeting = nullptr;
|
||||
std::unique_ptr<Session> m_session;
|
||||
std::string m_externalPath;
|
||||
|
||||
Wt::WContainerWidget *m_root = nullptr;
|
||||
};
|
||||
|
||||
class Server {
|
||||
friend class Amass::Singleton<Server>;
|
||||
|
||||
public:
|
||||
~WebApplication();
|
||||
~Server();
|
||||
|
||||
void initializeAuthenticationService();
|
||||
const Wt::Auth::AuthService &authService();
|
||||
const Wt::Auth::PasswordService &passwordService();
|
||||
|
||||
protected:
|
||||
WebApplication(uint16_t port, const std::string &documentRoot);
|
||||
Server(uint16_t port, const std::string &documentRoot);
|
||||
std::unique_ptr<Wt::WApplication> createApplication(const Wt::WEnvironment &env, bool embedded);
|
||||
|
||||
private:
|
||||
@ -36,5 +60,5 @@ private:
|
||||
std::unique_ptr<Wt::Auth::AuthService> m_authService;
|
||||
std::unique_ptr<Wt::Auth::PasswordService> m_passwordService;
|
||||
};
|
||||
|
||||
} // namespace WebToolkit
|
||||
#endif // __WEBAPPLICATION_H__
|
257
WebApplication/BulmaTheme.cpp
Normal file
257
WebApplication/BulmaTheme.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
#include "BulmaTheme.h"
|
||||
#include "BoostLog.h"
|
||||
#include <Wt/DomElement.h>
|
||||
#include <Wt/WDialog.h>
|
||||
#include <Wt/WLinkedCssStyleSheet.h>
|
||||
#include <Wt/WPopupWidget.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <format>
|
||||
|
||||
namespace std {
|
||||
std::ostream &operator<<(std::ostream &os, Wt::DomElementType type);
|
||||
}
|
||||
|
||||
std::string BulmaTheme::disabledClass() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BulmaTheme::activeClass() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BulmaTheme::utilityCssClass(int utilityCssClassRole) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<Wt::WLinkedCssStyleSheet> BulmaTheme::styleSheets() const {
|
||||
std::vector<Wt::WLinkedCssStyleSheet> result;
|
||||
std::string themeDir = resourcesUrl();
|
||||
if (m_global) {
|
||||
result.push_back(Wt::WLinkedCssStyleSheet(Wt::WLink(std::format("{}base.css", themeDir))));
|
||||
}
|
||||
result.push_back(Wt::WLinkedCssStyleSheet(Wt::WLink(std::format("{}prefixed.css", themeDir))));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BulmaTheme::canStyleAnchorAsButton() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void BulmaTheme::apply(Wt::WWidget *widget, Wt::WWidget *child, int widgetRole) const {
|
||||
}
|
||||
|
||||
void BulmaTheme::apply(Wt::WWidget *widget, Wt::DomElement &element, int elementRole) const {
|
||||
bool creating = element.mode() == Wt::DomElement::Mode::Create;
|
||||
if (!widget->isThemeStyleEnabled()) return;
|
||||
{
|
||||
Wt::WPopupWidget *popup = dynamic_cast<Wt::WPopupWidget *>(widget);
|
||||
if (popup) element.addPropertyWord(Wt::Property::Class, "Wt-outset");
|
||||
}
|
||||
switch (element.type()) {
|
||||
case Wt::DomElementType::BUTTON: {
|
||||
if (creating) {
|
||||
element.addPropertyWord(Wt::Property::Class, "bulma-button");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Wt::DomElementType::DIV: {
|
||||
Wt::WDialog *dialog = dynamic_cast<Wt::WDialog *>(widget);
|
||||
if (dialog) {
|
||||
element.addPropertyWord(Wt::Property::Class, "modal-content");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(warning) << "elemnet[" << element.type() << "] need style.";
|
||||
break;
|
||||
}
|
||||
|
||||
LOG(info) << "BulmaTheme::apply";
|
||||
}
|
||||
|
||||
void BulmaTheme::applyValidationStyle(Wt::WWidget *widget, const Wt::WValidator::Result &validation,
|
||||
Wt::WFlags<Wt::ValidationStyleFlag> flags) const {
|
||||
}
|
||||
|
||||
bool BulmaTheme::canBorderBoxElement(const Wt::DomElement &element) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string BulmaTheme::name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
BulmaTheme::BulmaTheme(const std::string &name, bool global) : m_name(name), m_global(global) {
|
||||
}
|
||||
|
||||
namespace std {
|
||||
std::ostream &operator<<(std::ostream &os, Wt::DomElementType type) {
|
||||
using namespace Wt;
|
||||
switch (type) {
|
||||
case DomElementType::A:
|
||||
os << "A";
|
||||
break;
|
||||
case DomElementType::BR:
|
||||
os << "BR";
|
||||
break;
|
||||
case DomElementType::BUTTON:
|
||||
os << "BUTTON";
|
||||
break;
|
||||
case DomElementType::COL:
|
||||
os << "COL";
|
||||
break;
|
||||
case DomElementType::COLGROUP:
|
||||
os << "COLGROUP";
|
||||
break;
|
||||
case DomElementType::DIV:
|
||||
os << "DIV";
|
||||
break;
|
||||
case DomElementType::FIELDSET:
|
||||
os << "FIELDSET";
|
||||
break;
|
||||
case DomElementType::FORM:
|
||||
os << "FORM";
|
||||
break;
|
||||
case DomElementType::H1:
|
||||
os << "H1";
|
||||
break;
|
||||
case DomElementType::H2:
|
||||
os << "H2";
|
||||
break;
|
||||
case DomElementType::H3:
|
||||
os << "H3";
|
||||
break;
|
||||
case DomElementType::H4:
|
||||
os << "H4";
|
||||
break;
|
||||
case DomElementType::H5:
|
||||
os << "H5";
|
||||
break;
|
||||
case DomElementType::H6:
|
||||
os << "H6";
|
||||
break;
|
||||
case DomElementType::IFRAME:
|
||||
os << "IFRAME";
|
||||
break;
|
||||
case DomElementType::IMG:
|
||||
os << "IMG";
|
||||
break;
|
||||
case DomElementType::INPUT:
|
||||
os << "INPUT";
|
||||
break;
|
||||
case DomElementType::LABEL:
|
||||
os << "LABEL";
|
||||
break;
|
||||
case DomElementType::LEGEND:
|
||||
os << "LEGEND";
|
||||
break;
|
||||
case DomElementType::LI:
|
||||
os << "LI";
|
||||
break;
|
||||
case DomElementType::OL:
|
||||
os << "OL";
|
||||
break;
|
||||
case DomElementType::OPTION:
|
||||
os << "OPTION";
|
||||
break;
|
||||
case DomElementType::UL:
|
||||
os << "UL";
|
||||
break;
|
||||
case DomElementType::SCRIPT:
|
||||
os << "SCRIPT";
|
||||
break;
|
||||
case DomElementType::SELECT:
|
||||
os << "SELECT";
|
||||
break;
|
||||
case DomElementType::SPAN:
|
||||
os << "SPAN";
|
||||
break;
|
||||
case DomElementType::TABLE:
|
||||
os << "TABLE";
|
||||
break;
|
||||
case DomElementType::TBODY:
|
||||
os << "TBODY";
|
||||
break;
|
||||
case DomElementType::THEAD:
|
||||
os << "THEAD";
|
||||
break;
|
||||
case DomElementType::TFOOT:
|
||||
os << "TFOOT";
|
||||
break;
|
||||
case DomElementType::TH:
|
||||
os << "TH";
|
||||
break;
|
||||
case DomElementType::TD:
|
||||
os << "TD";
|
||||
break;
|
||||
case DomElementType::TEXTAREA:
|
||||
os << "TEXTAREA";
|
||||
break;
|
||||
case DomElementType::OPTGROUP:
|
||||
os << "OPTGROUP";
|
||||
break;
|
||||
case DomElementType::TR:
|
||||
os << "TR";
|
||||
break;
|
||||
case DomElementType::P:
|
||||
os << "P";
|
||||
break;
|
||||
case DomElementType::CANVAS:
|
||||
os << "CANVAS";
|
||||
break;
|
||||
case DomElementType::MAP:
|
||||
os << "MAP";
|
||||
break;
|
||||
case DomElementType::AREA:
|
||||
os << "AREA";
|
||||
break;
|
||||
case DomElementType::STYLE:
|
||||
os << "STYLE";
|
||||
break;
|
||||
case DomElementType::OBJECT:
|
||||
os << "OBJECT";
|
||||
break;
|
||||
case DomElementType::PARAM:
|
||||
os << "PARAM";
|
||||
break;
|
||||
case DomElementType::AUDIO:
|
||||
os << "AUDIO";
|
||||
break;
|
||||
case DomElementType::VIDEO:
|
||||
os << "VIDEO";
|
||||
break;
|
||||
case DomElementType::SOURCE:
|
||||
os << "SOURCE";
|
||||
break;
|
||||
case DomElementType::B:
|
||||
os << "B";
|
||||
break;
|
||||
case DomElementType::STRONG:
|
||||
os << "STRONG";
|
||||
break;
|
||||
case DomElementType::EM:
|
||||
os << "EM";
|
||||
break;
|
||||
case DomElementType::I:
|
||||
os << "I";
|
||||
break;
|
||||
case DomElementType::HR:
|
||||
os << "HR";
|
||||
break;
|
||||
case DomElementType::DATALIST:
|
||||
os << "DATALIST";
|
||||
break;
|
||||
case DomElementType::UNKNOWN:
|
||||
os << "UNKNOWN";
|
||||
break;
|
||||
case DomElementType::OTHER:
|
||||
os << "OTHER";
|
||||
break;
|
||||
default:
|
||||
os << "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
} // namespace std
|
32
WebApplication/BulmaTheme.h
Normal file
32
WebApplication/BulmaTheme.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef __BULMATHEME_H__
|
||||
#define __BULMATHEME_H__
|
||||
|
||||
#include <Wt/WTheme.h>
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Animate.css
|
||||
* https://bulma.io/
|
||||
*/
|
||||
class BulmaTheme : public Wt::WTheme {
|
||||
public:
|
||||
BulmaTheme(const std::string &name, bool global = true);
|
||||
std::string name() const final;
|
||||
std::string disabledClass() const final;
|
||||
std::string activeClass() const final;
|
||||
std::string utilityCssClass(int utilityCssClassRole) const final;
|
||||
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const final;
|
||||
bool canStyleAnchorAsButton() const final;
|
||||
void apply(Wt::WWidget *widget, Wt::WWidget *child, int widgetRole) const final;
|
||||
void apply(Wt::WWidget *widget, Wt::DomElement &element, int elementRole) const final;
|
||||
|
||||
void applyValidationStyle(Wt::WWidget *widget, const Wt::WValidator::Result &validation,
|
||||
Wt::WFlags<Wt::ValidationStyleFlag> flags) const final;
|
||||
bool canBorderBoxElement(const Wt::DomElement &element) const final;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
bool m_global = true;
|
||||
};
|
||||
|
||||
#endif // __BULMATHEME_H__
|
@ -1,17 +1,19 @@
|
||||
find_package(Wt REQUIRED Wt)
|
||||
|
||||
add_library(WebApplication
|
||||
WebApplication.h WebApplication.cpp
|
||||
Application.h Application.cpp
|
||||
LoginPage.h LoginPage.cpp
|
||||
BulmaTheme.h BulmaTheme.cpp
|
||||
VisitorRecordsPage.h VisitorRecordsPage.cpp
|
||||
VisitorRecordTableModel.h VisitorRecordTableModel.cpp
|
||||
Hello.h Hello.cpp
|
||||
Restful.h Restful.cpp
|
||||
Dialog.h Dialog.cpp
|
||||
)
|
||||
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
|
||||
|
||||
target_include_directories(WebApplication
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
INTERFACE ${PARENT_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(WebApplication
|
||||
|
@ -1,108 +0,0 @@
|
||||
#include "Hello.h"
|
||||
#include "BoostLog.h"
|
||||
#include "Database/Session.h"
|
||||
#include "Dialog.h"
|
||||
#include "LoginPage.h"
|
||||
#include "VisitorRecordsPage.h"
|
||||
#include "WebApplication.h"
|
||||
#include <Wt/Auth/Identity.h>
|
||||
#include <Wt/WBootstrap2Theme.h>
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <Wt/WEnvironment.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WText.h>
|
||||
|
||||
Hello::Hello(const Wt::WEnvironment &env, bool embedded) : Wt::WApplication(env) {
|
||||
messageResourceBundle().use(appRoot() + "wt");
|
||||
messageResourceBundle().use(appRoot() + "auth_strings");
|
||||
messageResourceBundle().use(appRoot() + "auth_css_theme");
|
||||
useStyleSheet("/resources/app.css");
|
||||
LOG(info) << "app root: " << appRoot();
|
||||
m_session = Database::session();
|
||||
m_session->login().changed().connect(this, &Hello::authEvent);
|
||||
setTitle("Hello world");
|
||||
if (!embedded) {
|
||||
setTheme(std::make_shared<Wt::WBootstrap2Theme>());
|
||||
m_root = root();
|
||||
} else {
|
||||
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
|
||||
m_root = topPtr.get();
|
||||
const std::string *div = env.getParameter("div");
|
||||
if (div) {
|
||||
setJavaScriptClass(*div);
|
||||
bindWidget(std::move(topPtr), *div);
|
||||
} else {
|
||||
LOG(error) << "Missing: parameter: 'div'";
|
||||
}
|
||||
auto externalPath = env.getParameter("path");
|
||||
if (externalPath != nullptr) {
|
||||
m_externalPath = *externalPath;
|
||||
LOG(info) << "external path: " << m_externalPath;
|
||||
} else {
|
||||
auto parameters = env.getParameterMap();
|
||||
for (auto &p : parameters) {
|
||||
LOG(info) << p.first;
|
||||
}
|
||||
}
|
||||
LOG(info) << "url: " << url();
|
||||
LOG(info) << "relative resources url: " << relativeResourcesUrl();
|
||||
LOG(info) << "resources url: " << resourcesUrl();
|
||||
}
|
||||
|
||||
if (!embedded) {
|
||||
root()->addWidget(std::make_unique<Wt::WText>("<p><emph>Note: you can also run this application "
|
||||
"from within <a href=\"hello.html\">a web page</a>.</emph></p>"));
|
||||
}
|
||||
LOG(info) << "internal path: " << internalPath();
|
||||
|
||||
m_root->addWidget(std::make_unique<Wt::WText>("Your name, please ? "));
|
||||
m_nameEdit = m_root->addWidget(std::make_unique<Wt::WLineEdit>());
|
||||
m_nameEdit->setFocus();
|
||||
|
||||
auto b = m_root->addWidget(std::make_unique<Wt::WPushButton>("点击我!"));
|
||||
b->setMargin(5, Wt::Side::Left);
|
||||
|
||||
m_root->addWidget(std::make_unique<Wt::WBreak>());
|
||||
|
||||
m_greeting = m_root->addWidget(std::make_unique<Wt::WText>());
|
||||
|
||||
b->clicked().connect(this, &Hello::greet);
|
||||
m_nameEdit->enterPressed().connect(this, &Hello::greet);
|
||||
|
||||
auto app = Amass::Singleton<WebApplication>::instance();
|
||||
|
||||
m_root->addWidget(std::make_unique<Dialog>());
|
||||
internalPathChanged().connect(this, &Hello::handlePathChange);
|
||||
handlePathChange(m_externalPath.empty() ? internalPath() : m_externalPath);
|
||||
}
|
||||
|
||||
Hello::~Hello() {
|
||||
}
|
||||
|
||||
void Hello::greet() {
|
||||
m_greeting->setText("Hello there, " + m_nameEdit->text());
|
||||
setInternalPath(m_externalPath);
|
||||
}
|
||||
|
||||
void Hello::authEvent() {
|
||||
if (m_session->login().loggedIn()) {
|
||||
const Wt::Auth::User &u = m_session->login().user();
|
||||
LOG(info) << "User " << u.id() << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")"
|
||||
<< " logged in.";
|
||||
} else {
|
||||
LOG(info) << "User logged out.";
|
||||
}
|
||||
}
|
||||
|
||||
void Hello::handlePathChange(const std::string &path) {
|
||||
LOG(info) << "handlePathChange: " << path;
|
||||
if (path.starts_with("/wt/login") || path.starts_with("/wt/register")) {
|
||||
m_root->clear();
|
||||
m_root->setStyleClass("WtCenterContainer");
|
||||
m_root->addNew<LoginWidget>(m_session->users(), m_session->login());
|
||||
} else if (path.starts_with("/wt/visitor/analysis")) {
|
||||
m_root->clear();
|
||||
m_root->addNew<VisitorRecordsPage>(*m_session);
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
#ifndef __HELLO_H__
|
||||
#define __HELLO_H__
|
||||
|
||||
#include <Wt/WApplication.h>
|
||||
|
||||
class Session;
|
||||
|
||||
class Hello : public Wt::WApplication {
|
||||
public:
|
||||
Hello(const Wt::WEnvironment &env, bool embedded);
|
||||
~Hello();
|
||||
|
||||
protected:
|
||||
void greet();
|
||||
void authEvent();
|
||||
void handlePathChange(const std::string &path);
|
||||
|
||||
private:
|
||||
Wt::WLineEdit *m_nameEdit = nullptr;
|
||||
Wt::WText *m_greeting = nullptr;
|
||||
std::unique_ptr<Session> m_session;
|
||||
std::string m_externalPath;
|
||||
|
||||
Wt::WContainerWidget *m_root = nullptr;
|
||||
};
|
||||
|
||||
#endif // __HELLO_H__
|
@ -1,12 +1,12 @@
|
||||
#include "LoginPage.h"
|
||||
#include "WebApplication.h"
|
||||
#include "Application.h"
|
||||
#include <Wt/Auth/AuthService.h>
|
||||
#include <Wt/Auth/AuthWidget.h>
|
||||
#include <Wt/Auth/PasswordService.h>
|
||||
#include <Wt/WVBoxLayout.h>
|
||||
|
||||
LoginWidget::LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) {
|
||||
auto app = Amass::Singleton<WebApplication>::instance();
|
||||
LoginPage::LoginPage(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) {
|
||||
auto app = Amass::Singleton<WebToolkit::Server>::instance();
|
||||
auto authWidget = std::make_unique<Wt::Auth::AuthWidget>(app->authService(), users, login);
|
||||
authWidget->setInternalBasePath("/wt");
|
||||
authWidget->model()->addPasswordAuth(&app->passwordService());
|
||||
@ -14,6 +14,4 @@ LoginWidget::LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login
|
||||
authWidget->processEnvironment();
|
||||
// setAttributeValue("style", "transform: translateY(-100px);");
|
||||
addWidget(std::move(authWidget));
|
||||
|
||||
authWidget-> resolve<WInteractWidget *>("login")->addStyleClass("button--primary");
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
#include <Wt/WContainerWidget.h>
|
||||
|
||||
class LoginWidget : public Wt::WContainerWidget {
|
||||
class LoginPage : public Wt::WContainerWidget {
|
||||
public:
|
||||
LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login);
|
||||
LoginPage(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login);
|
||||
};
|
||||
|
||||
#endif // __LOGINWIDGET_H__
|
@ -75,11 +75,11 @@ bool VisitorRecordTableModel::setHeaderData(int section, Wt::Orientation orienta
|
||||
void VisitorRecordTableModel::sort(int column, Wt::SortOrder order) {
|
||||
if (column == 2) {
|
||||
layoutAboutToBeChanged().emit();
|
||||
std::sort(m_records.begin(), m_records.end(), [order](const VisitorRecord &lfh, const VisitorRecord &rfh) {
|
||||
std::sort(m_records.begin(), m_records.end(), [order](const Item &lfh, const Item &rfh) {
|
||||
if (order == Wt::SortOrder::Ascending) {
|
||||
return lfh.time < rfh.time;
|
||||
return std::get<0>(lfh).time < std::get<0>(rfh).time;
|
||||
} else {
|
||||
return lfh.time > rfh.time;
|
||||
return std::get<0>(lfh).time > std::get<0>(rfh).time;
|
||||
}
|
||||
});
|
||||
layoutChanged().emit();
|
||||
@ -144,27 +144,45 @@ bool VisitorRecordTableModel::setData(const Wt::WModelIndex &index, const Wt::cp
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VisitorRecord> VisitorRecordTableModel::selected() const {
|
||||
std::vector<VisitorRecord> ret;
|
||||
Wt::WModelIndexSet VisitorRecordTableModel::selectedIndexes() const {
|
||||
Wt::WModelIndexSet ret;
|
||||
for (int i = 0; i < m_records.size(); i++) {
|
||||
if (std::get<1>(m_records.at(i))) {
|
||||
ret.push_back(std::get<0>(m_records.at(i)));
|
||||
ret.insert(index(i, 0));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VisitorRecordTableModel::removeSelected() {
|
||||
bool VisitorRecordTableModel::removeSelectedRows(const Wt::WModelIndexSet &indexes) {
|
||||
Wt::Dbo::Transaction transaction(m_session);
|
||||
for (auto iterator = m_records.cbegin(); iterator != m_records.cend();) {
|
||||
auto selected = std::get<1>(*iterator);
|
||||
auto &r = std::get<0>(*iterator);
|
||||
if (selected) {
|
||||
Wt::Dbo::ptr<VisitorRecord> item = m_session.find<VisitorRecord>().where("id = ?").bind(r.id);
|
||||
item.remove();
|
||||
iterator = m_records.erase(iterator);
|
||||
} else {
|
||||
++iterator;
|
||||
}
|
||||
layoutAboutToBeChanged().emit();
|
||||
std::vector<int> ids;
|
||||
for (auto &index : indexes) {
|
||||
auto &r = std::get<0>(m_records.at(index.row()));
|
||||
ids.push_back(r.id);
|
||||
Wt::Dbo::ptr<VisitorRecord> item = m_session.find<VisitorRecord>().where("id = ?").bind(r.id);
|
||||
item.remove();
|
||||
}
|
||||
}
|
||||
for (auto id : ids) {
|
||||
std::erase_if(m_records, [id](const Item &item) { return std::get<0>(item).id == id; });
|
||||
}
|
||||
layoutChanged().emit();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitorRecordTableModel::removeRows(int row, int count, const Wt::WModelIndex &parent) {
|
||||
if ((row + count) > m_records.size()) {
|
||||
return false;
|
||||
}
|
||||
Wt::Dbo::Transaction transaction(m_session);
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int i = row; i < (row + count); ++i) {
|
||||
auto &r = std::get<0>(m_records.at(i));
|
||||
Wt::Dbo::ptr<VisitorRecord> item = m_session.find<VisitorRecord>().where("id = ?").bind(r.id);
|
||||
item.remove();
|
||||
}
|
||||
m_records.erase(m_records.cbegin() + row, m_records.cbegin() + row + count);
|
||||
endRemoveRows();
|
||||
return true;
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ public:
|
||||
bool setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value,
|
||||
Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final;
|
||||
void sort(int column, Wt::SortOrder order = Wt::SortOrder::Ascending) final;
|
||||
Wt::WModelIndexSet selectedIndexes() const;
|
||||
|
||||
std::vector<VisitorRecord> selected() const;
|
||||
void removeSelected();
|
||||
bool removeRows(int row, int count, const Wt::WModelIndex &parent = Wt::WModelIndex()) final;
|
||||
bool removeSelectedRows(const Wt::WModelIndexSet &indexes);
|
||||
|
||||
private:
|
||||
Session &m_session;
|
||||
|
@ -50,13 +50,15 @@ VisitorRecordsPage::VisitorRecordsPage(Session &session) : m_model{std::make_sha
|
||||
}
|
||||
|
||||
void VisitorRecordsPage::onDeleteButtonClicked() {
|
||||
auto selected = m_model->selected();
|
||||
auto selected = m_model->selectedIndexes();
|
||||
if (selected.empty()) {
|
||||
Wt::WMessageBox::show("删除记录", "请先选择要删除的记录", Wt::StandardButton::Ok);
|
||||
} else {
|
||||
auto anwser =
|
||||
Wt::WMessageBox::show("删除记录", "确定删除选择的记录?", Wt::StandardButton::Ok | Wt::StandardButton::Cancel);
|
||||
if (anwser == Wt::StandardButton::Ok) {
|
||||
LOG(info) << "remove row: " << selected.size();
|
||||
m_model->removeSelectedRows(selected);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
#include "WebApplication.h"
|
||||
#include "BoostLog.h"
|
||||
#include "Database/Session.h"
|
||||
#include "Hello.h"
|
||||
#include "Restful.h"
|
||||
#include <Wt/Auth/AuthService.h>
|
||||
#include <Wt/Auth/HashFunction.h>
|
||||
#include <Wt/Auth/PasswordService.h>
|
||||
#include <Wt/Auth/PasswordStrengthValidator.h>
|
||||
#include <Wt/Auth/PasswordVerifier.h>
|
||||
#include <Wt/Dbo/FixedSqlConnectionPool.h>
|
||||
#include <Wt/Dbo/SqlConnectionPool.h>
|
||||
#include <Wt/Dbo/backend/Sqlite3.h>
|
||||
#include <Wt/WServer.h>
|
||||
#include <format>
|
||||
|
||||
WebApplication::WebApplication(uint16_t port, const std::string &documentRoot) {
|
||||
try {
|
||||
std::vector<std::string> args;
|
||||
args.push_back(std::format("--docroot={};/resources", documentRoot));
|
||||
args.push_back(std::format("--approot={}/resources", documentRoot));
|
||||
args.push_back(std::format("--http-listen=127.0.0.1:{}", port));
|
||||
initializeAuthenticationService();
|
||||
|
||||
m_server = std::make_unique<Wt::WServer>(std::format("{}/resources", documentRoot), args);
|
||||
|
||||
m_server->addEntryPoint(Wt::EntryPointType::Application,
|
||||
std::bind(&WebApplication::createApplication, this, std::placeholders::_1, false));
|
||||
m_server->addEntryPoint(Wt::EntryPointType::WidgetSet,
|
||||
std::bind(&WebApplication::createApplication, this, std::placeholders::_1, true), "/wt/app.js");
|
||||
m_server->addResource(std::make_shared<AuthenticationResource>(), "/auth");
|
||||
m_server->addResource(std::make_shared<PlaintextResource>(), "/plaintext");
|
||||
m_server->addResource(std::make_shared<DbResource>(std::format("{}/database.sqlite", documentRoot)), "/db");
|
||||
|
||||
m_server->start();
|
||||
} catch (const std::exception &e) {
|
||||
LOG(error) << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WApplication> WebApplication::createApplication(const Wt::WEnvironment &env, bool embedded) {
|
||||
return std::make_unique<Hello>(env, embedded);
|
||||
}
|
||||
|
||||
WebApplication::~WebApplication() {
|
||||
}
|
||||
|
||||
void WebApplication::initializeAuthenticationService() {
|
||||
m_authService = std::make_unique<Wt::Auth::AuthService>();
|
||||
m_authService->setAuthTokensEnabled(true, "logincookie");
|
||||
m_passwordService = std::make_unique<Wt::Auth::PasswordService>(*m_authService);
|
||||
|
||||
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
|
||||
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
|
||||
m_passwordService->setVerifier(std::move(verifier));
|
||||
m_passwordService->setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
|
||||
m_passwordService->setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
|
||||
}
|
||||
|
||||
const Wt::Auth::AuthService &WebApplication::authService() {
|
||||
return *m_authService;
|
||||
}
|
||||
|
||||
const Wt::Auth::PasswordService &WebApplication::passwordService() {
|
||||
return *m_passwordService;
|
||||
}
|
@ -25,7 +25,7 @@ function build() {
|
||||
# reset
|
||||
# pkill -9 HttpServer
|
||||
# cp -r /opt/Libraries/wt-4.11.1/share/Wt/* ./build
|
||||
# cp resources/* build/resources/
|
||||
# cp -r resources/* build/resources/
|
||||
|
||||
if [ ! -f "${build_path}/CMakeCache.txt" ]; then
|
||||
cmake_scan
|
||||
|
Loading…
Reference in New Issue
Block a user