add bulma theme.
All checks were successful
Deploy / Build (push) Successful in 7m22s

This commit is contained in:
amass 2024-12-01 15:10:25 +08:00
parent 6803f84677
commit 8229aab949
15 changed files with 543 additions and 240 deletions

View File

@ -10,7 +10,7 @@
#include "UdpServer.h" #include "UdpServer.h"
#include "WeChatContext/CorporationContext.h" #include "WeChatContext/CorporationContext.h"
#include "WeChatContext/WeChatContext.h" #include "WeChatContext/WeChatContext.h"
#include "WebApplication.h" #include "WebApplication/Application.h"
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.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 udpServer = std::make_shared<UdpServer>(application->ioContext());
auto mediaServer = std::make_shared<MediaServer>(554, false); 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; using namespace boost::asio::ip;
auto proxyAddress = make_address(application->getServer()); auto proxyAddress = make_address(application->getServer());

View 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

View File

@ -2,6 +2,7 @@
#define __WEBAPPLICATION_H__ #define __WEBAPPLICATION_H__
#include "Singleton.h" #include "Singleton.h"
#include <Wt/WApplication.h>
#include <memory> #include <memory>
namespace Wt { namespace Wt {
@ -16,18 +17,41 @@ class PasswordService;
}; // namespace Wt }; // namespace Wt
class WebApplication { class Session;
friend class Amass::Singleton<WebApplication>;
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: public:
~WebApplication(); ~Server();
void initializeAuthenticationService(); void initializeAuthenticationService();
const Wt::Auth::AuthService &authService(); const Wt::Auth::AuthService &authService();
const Wt::Auth::PasswordService &passwordService(); const Wt::Auth::PasswordService &passwordService();
protected: 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); std::unique_ptr<Wt::WApplication> createApplication(const Wt::WEnvironment &env, bool embedded);
private: private:
@ -36,5 +60,5 @@ private:
std::unique_ptr<Wt::Auth::AuthService> m_authService; std::unique_ptr<Wt::Auth::AuthService> m_authService;
std::unique_ptr<Wt::Auth::PasswordService> m_passwordService; std::unique_ptr<Wt::Auth::PasswordService> m_passwordService;
}; };
} // namespace WebToolkit
#endif // __WEBAPPLICATION_H__ #endif // __WEBAPPLICATION_H__

View 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

View 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__

View File

@ -1,17 +1,19 @@
find_package(Wt REQUIRED Wt) find_package(Wt REQUIRED Wt)
add_library(WebApplication add_library(WebApplication
WebApplication.h WebApplication.cpp Application.h Application.cpp
LoginPage.h LoginPage.cpp LoginPage.h LoginPage.cpp
BulmaTheme.h BulmaTheme.cpp
VisitorRecordsPage.h VisitorRecordsPage.cpp VisitorRecordsPage.h VisitorRecordsPage.cpp
VisitorRecordTableModel.h VisitorRecordTableModel.cpp VisitorRecordTableModel.h VisitorRecordTableModel.cpp
Hello.h Hello.cpp
Restful.h Restful.cpp Restful.h Restful.cpp
Dialog.h Dialog.cpp Dialog.h Dialog.cpp
) )
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
target_include_directories(WebApplication target_include_directories(WebApplication
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} INTERFACE ${PARENT_DIR}
) )
target_link_libraries(WebApplication target_link_libraries(WebApplication

View File

@ -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);
}
}

View File

@ -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__

View File

@ -1,12 +1,12 @@
#include "LoginPage.h" #include "LoginPage.h"
#include "WebApplication.h" #include "Application.h"
#include <Wt/Auth/AuthService.h> #include <Wt/Auth/AuthService.h>
#include <Wt/Auth/AuthWidget.h> #include <Wt/Auth/AuthWidget.h>
#include <Wt/Auth/PasswordService.h> #include <Wt/Auth/PasswordService.h>
#include <Wt/WVBoxLayout.h> #include <Wt/WVBoxLayout.h>
LoginWidget::LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) { LoginPage::LoginPage(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) {
auto app = Amass::Singleton<WebApplication>::instance(); auto app = Amass::Singleton<WebToolkit::Server>::instance();
auto authWidget = std::make_unique<Wt::Auth::AuthWidget>(app->authService(), users, login); auto authWidget = std::make_unique<Wt::Auth::AuthWidget>(app->authService(), users, login);
authWidget->setInternalBasePath("/wt"); authWidget->setInternalBasePath("/wt");
authWidget->model()->addPasswordAuth(&app->passwordService()); authWidget->model()->addPasswordAuth(&app->passwordService());
@ -14,6 +14,4 @@ LoginWidget::LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login
authWidget->processEnvironment(); authWidget->processEnvironment();
// setAttributeValue("style", "transform: translateY(-100px);"); // setAttributeValue("style", "transform: translateY(-100px);");
addWidget(std::move(authWidget)); addWidget(std::move(authWidget));
authWidget-> resolve<WInteractWidget *>("login")->addStyleClass("button--primary");
} }

View File

@ -3,9 +3,9 @@
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
class LoginWidget : public Wt::WContainerWidget { class LoginPage : public Wt::WContainerWidget {
public: public:
LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login); LoginPage(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login);
}; };
#endif // __LOGINWIDGET_H__ #endif // __LOGINWIDGET_H__

View File

@ -75,11 +75,11 @@ bool VisitorRecordTableModel::setHeaderData(int section, Wt::Orientation orienta
void VisitorRecordTableModel::sort(int column, Wt::SortOrder order) { void VisitorRecordTableModel::sort(int column, Wt::SortOrder order) {
if (column == 2) { if (column == 2) {
layoutAboutToBeChanged().emit(); 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) { if (order == Wt::SortOrder::Ascending) {
return lfh.time < rfh.time; return std::get<0>(lfh).time < std::get<0>(rfh).time;
} else { } else {
return lfh.time > rfh.time; return std::get<0>(lfh).time > std::get<0>(rfh).time;
} }
}); });
layoutChanged().emit(); layoutChanged().emit();
@ -144,27 +144,45 @@ bool VisitorRecordTableModel::setData(const Wt::WModelIndex &index, const Wt::cp
} }
} }
std::vector<VisitorRecord> VisitorRecordTableModel::selected() const { Wt::WModelIndexSet VisitorRecordTableModel::selectedIndexes() const {
std::vector<VisitorRecord> ret; Wt::WModelIndexSet ret;
for (int i = 0; i < m_records.size(); i++) { for (int i = 0; i < m_records.size(); i++) {
if (std::get<1>(m_records.at(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; return ret;
} }
void VisitorRecordTableModel::removeSelected() { bool VisitorRecordTableModel::removeSelectedRows(const Wt::WModelIndexSet &indexes) {
Wt::Dbo::Transaction transaction(m_session); Wt::Dbo::Transaction transaction(m_session);
for (auto iterator = m_records.cbegin(); iterator != m_records.cend();) { layoutAboutToBeChanged().emit();
auto selected = std::get<1>(*iterator); std::vector<int> ids;
auto &r = std::get<0>(*iterator); for (auto &index : indexes) {
if (selected) { 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); Wt::Dbo::ptr<VisitorRecord> item = m_session.find<VisitorRecord>().where("id = ?").bind(r.id);
item.remove(); item.remove();
iterator = m_records.erase(iterator);
} else {
++iterator;
} }
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;
} }

View File

@ -20,9 +20,10 @@ public:
bool setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value, bool setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value,
Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final; Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final;
void sort(int column, Wt::SortOrder order = Wt::SortOrder::Ascending) final; void sort(int column, Wt::SortOrder order = Wt::SortOrder::Ascending) final;
Wt::WModelIndexSet selectedIndexes() const;
std::vector<VisitorRecord> selected() const; bool removeRows(int row, int count, const Wt::WModelIndex &parent = Wt::WModelIndex()) final;
void removeSelected(); bool removeSelectedRows(const Wt::WModelIndexSet &indexes);
private: private:
Session &m_session; Session &m_session;

View File

@ -50,13 +50,15 @@ VisitorRecordsPage::VisitorRecordsPage(Session &session) : m_model{std::make_sha
} }
void VisitorRecordsPage::onDeleteButtonClicked() { void VisitorRecordsPage::onDeleteButtonClicked() {
auto selected = m_model->selected(); auto selected = m_model->selectedIndexes();
if (selected.empty()) { if (selected.empty()) {
Wt::WMessageBox::show("删除记录", "请先选择要删除的记录", Wt::StandardButton::Ok); Wt::WMessageBox::show("删除记录", "请先选择要删除的记录", Wt::StandardButton::Ok);
} else { } else {
auto anwser = auto anwser =
Wt::WMessageBox::show("删除记录", "确定删除选择的记录?", Wt::StandardButton::Ok | Wt::StandardButton::Cancel); Wt::WMessageBox::show("删除记录", "确定删除选择的记录?", Wt::StandardButton::Ok | Wt::StandardButton::Cancel);
if (anwser == Wt::StandardButton::Ok) { if (anwser == Wt::StandardButton::Ok) {
LOG(info) << "remove row: " << selected.size();
m_model->removeSelectedRows(selected);
} }
} }
} }

View File

@ -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;
}

View File

@ -25,7 +25,7 @@ function build() {
# reset # reset
# pkill -9 HttpServer # pkill -9 HttpServer
# cp -r /opt/Libraries/wt-4.11.1/share/Wt/* ./build # 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 if [ ! -f "${build_path}/CMakeCache.txt" ]; then
cmake_scan cmake_scan