diff --git a/Server/main.cpp b/Server/main.cpp index 7cd649f..6f34c86 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -10,7 +10,7 @@ #include "UdpServer.h" #include "WeChatContext/CorporationContext.h" #include "WeChatContext/WeChatContext.h" -#include "WebApplication.h" +#include "WebApplication/Application.h" #include #include #include @@ -97,7 +97,7 @@ int main(int argc, char const *argv[]) { auto udpServer = std::make_shared(application->ioContext()); auto mediaServer = std::make_shared(554, false); - auto webApp = Singleton::instance(application->getWtPort(), application->getDocumentRoot()); + auto webApp = Singleton::instance(application->getWtPort(), application->getDocumentRoot()); using namespace boost::asio::ip; auto proxyAddress = make_address(application->getServer()); diff --git a/WebApplication/Application.cpp b/WebApplication/Application.cpp new file mode 100644 index 0000000..81d961f --- /dev/null +++ b/WebApplication/Application.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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("bulma", !embedded)); + if (!embedded) { + // setTheme(std::make_shared()); + m_root = root(); + } else { + std::unique_ptr topPtr = std::make_unique(); + 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( + "Note: you can also run this application from within a web page.")); + } + LOG(info) << "internal path: " << internalPath(); + + m_root->addWidget(std::make_unique("Your name, please ? ")); + m_nameEdit = m_root->addWidget(std::make_unique()); + m_nameEdit->setFocus(); + + auto b = m_root->addWidget(std::make_unique("点击我!")); + b->setMargin(5, Wt::Side::Left); + + m_root->addWidget(std::make_unique()); + + m_greeting = m_root->addWidget(std::make_unique()); + + b->clicked().connect(this, &Application::greet); + m_nameEdit->enterPressed().connect(this, &Application::greet); + + auto app = Amass::Singleton::instance(); + + m_root->addWidget(std::make_unique()); + 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(m_session->users(), m_session->login()); + } else if (path.starts_with("/wt/visitor/analysis")) { + m_root->clear(); + m_root->addNew(*m_session); + } +} + +Server::Server(uint16_t port, const std::string &documentRoot) { + try { + std::vector 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(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(), "/auth"); + m_server->addResource(std::make_shared(), "/plaintext"); + m_server->addResource(std::make_shared(std::format("{}/database.sqlite", documentRoot)), "/db"); + + m_server->start(); + } catch (const std::exception &e) { + LOG(error) << e.what(); + } +} + +std::unique_ptr Server::createApplication(const Wt::WEnvironment &env, bool embedded) { + return std::make_unique(env, embedded); +} + +Server::~Server() { +} + +void Server::initializeAuthenticationService() { + m_authService = std::make_unique(); + m_authService->setAuthTokensEnabled(true, "logincookie"); + m_passwordService = std::make_unique(*m_authService); + + auto verifier = std::make_unique(); + verifier->addHashFunction(std::make_unique(7)); + m_passwordService->setVerifier(std::move(verifier)); + m_passwordService->setPasswordThrottle(std::make_unique()); + m_passwordService->setStrengthValidator(std::make_unique()); +} + +const Wt::Auth::AuthService &Server::authService() { + return *m_authService; +} + +const Wt::Auth::PasswordService &Server::passwordService() { + return *m_passwordService; +} +} // namespace WebToolkit \ No newline at end of file diff --git a/WebApplication/WebApplication.h b/WebApplication/Application.h similarity index 52% rename from WebApplication/WebApplication.h rename to WebApplication/Application.h index 19f0c9c..fb312b3 100644 --- a/WebApplication/WebApplication.h +++ b/WebApplication/Application.h @@ -2,6 +2,7 @@ #define __WEBAPPLICATION_H__ #include "Singleton.h" +#include #include namespace Wt { @@ -16,18 +17,41 @@ class PasswordService; }; // namespace Wt -class WebApplication { - friend class Amass::Singleton; +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 m_session; + std::string m_externalPath; + + Wt::WContainerWidget *m_root = nullptr; +}; + +class Server { + friend class Amass::Singleton; 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 createApplication(const Wt::WEnvironment &env, bool embedded); private: @@ -36,5 +60,5 @@ private: std::unique_ptr m_authService; std::unique_ptr m_passwordService; }; - +} // namespace WebToolkit #endif // __WEBAPPLICATION_H__ \ No newline at end of file diff --git a/WebApplication/BulmaTheme.cpp b/WebApplication/BulmaTheme.cpp new file mode 100644 index 0000000..4c26411 --- /dev/null +++ b/WebApplication/BulmaTheme.cpp @@ -0,0 +1,257 @@ +#include "BulmaTheme.h" +#include "BoostLog.h" +#include +#include +#include +#include +#include +#include + +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 BulmaTheme::styleSheets() const { + std::vector 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(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(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 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 \ No newline at end of file diff --git a/WebApplication/BulmaTheme.h b/WebApplication/BulmaTheme.h new file mode 100644 index 0000000..6cf1ebc --- /dev/null +++ b/WebApplication/BulmaTheme.h @@ -0,0 +1,32 @@ +#ifndef __BULMATHEME_H__ +#define __BULMATHEME_H__ + +#include + +/** + * @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 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 flags) const final; + bool canBorderBoxElement(const Wt::DomElement &element) const final; + +private: + std::string m_name; + bool m_global = true; +}; + +#endif // __BULMATHEME_H__ \ No newline at end of file diff --git a/WebApplication/CMakeLists.txt b/WebApplication/CMakeLists.txt index 24d216b..86ceb49 100644 --- a/WebApplication/CMakeLists.txt +++ b/WebApplication/CMakeLists.txt @@ -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 diff --git a/WebApplication/Hello.cpp b/WebApplication/Hello.cpp deleted file mode 100644 index 6cb1ee0..0000000 --- a/WebApplication/Hello.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include - -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()); - m_root = root(); - } else { - std::unique_ptr topPtr = std::make_unique(); - 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("

Note: you can also run this application " - "from within a web page.

")); - } - LOG(info) << "internal path: " << internalPath(); - - m_root->addWidget(std::make_unique("Your name, please ? ")); - m_nameEdit = m_root->addWidget(std::make_unique()); - m_nameEdit->setFocus(); - - auto b = m_root->addWidget(std::make_unique("点击我!")); - b->setMargin(5, Wt::Side::Left); - - m_root->addWidget(std::make_unique()); - - m_greeting = m_root->addWidget(std::make_unique()); - - b->clicked().connect(this, &Hello::greet); - m_nameEdit->enterPressed().connect(this, &Hello::greet); - - auto app = Amass::Singleton::instance(); - - m_root->addWidget(std::make_unique()); - 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(m_session->users(), m_session->login()); - } else if (path.starts_with("/wt/visitor/analysis")) { - m_root->clear(); - m_root->addNew(*m_session); - } -} diff --git a/WebApplication/Hello.h b/WebApplication/Hello.h deleted file mode 100644 index 2a703eb..0000000 --- a/WebApplication/Hello.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef __HELLO_H__ -#define __HELLO_H__ - -#include - -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 m_session; - std::string m_externalPath; - - Wt::WContainerWidget *m_root = nullptr; -}; - -#endif // __HELLO_H__ \ No newline at end of file diff --git a/WebApplication/LoginPage.cpp b/WebApplication/LoginPage.cpp index 36ca9b2..927a979 100644 --- a/WebApplication/LoginPage.cpp +++ b/WebApplication/LoginPage.cpp @@ -1,12 +1,12 @@ #include "LoginPage.h" -#include "WebApplication.h" +#include "Application.h" #include #include #include #include -LoginWidget::LoginWidget(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) { - auto app = Amass::Singleton::instance(); +LoginPage::LoginPage(Wt::Auth::AbstractUserDatabase &users, Wt::Auth::Login &login) { + auto app = Amass::Singleton::instance(); auto authWidget = std::make_unique(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("login")->addStyleClass("button--primary"); } diff --git a/WebApplication/LoginPage.h b/WebApplication/LoginPage.h index 60e4407..4a68680 100644 --- a/WebApplication/LoginPage.h +++ b/WebApplication/LoginPage.h @@ -3,9 +3,9 @@ #include -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__ \ No newline at end of file diff --git a/WebApplication/VisitorRecordTableModel.cpp b/WebApplication/VisitorRecordTableModel.cpp index 2524cfa..b5b79f0 100644 --- a/WebApplication/VisitorRecordTableModel.cpp +++ b/WebApplication/VisitorRecordTableModel.cpp @@ -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 VisitorRecordTableModel::selected() const { - std::vector 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 item = m_session.find().where("id = ?").bind(r.id); - item.remove(); - iterator = m_records.erase(iterator); - } else { - ++iterator; - } + layoutAboutToBeChanged().emit(); + std::vector ids; + for (auto &index : indexes) { + auto &r = std::get<0>(m_records.at(index.row())); + ids.push_back(r.id); + Wt::Dbo::ptr item = m_session.find().where("id = ?").bind(r.id); + item.remove(); } -} \ No newline at end of file + 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 item = m_session.find().where("id = ?").bind(r.id); + item.remove(); + } + m_records.erase(m_records.cbegin() + row, m_records.cbegin() + row + count); + endRemoveRows(); + return true; +} diff --git a/WebApplication/VisitorRecordTableModel.h b/WebApplication/VisitorRecordTableModel.h index b9acf9a..348b0c5 100644 --- a/WebApplication/VisitorRecordTableModel.h +++ b/WebApplication/VisitorRecordTableModel.h @@ -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 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; diff --git a/WebApplication/VisitorRecordsPage.cpp b/WebApplication/VisitorRecordsPage.cpp index aa472d6..a8fe62c 100644 --- a/WebApplication/VisitorRecordsPage.cpp +++ b/WebApplication/VisitorRecordsPage.cpp @@ -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); } } } \ No newline at end of file diff --git a/WebApplication/WebApplication.cpp b/WebApplication/WebApplication.cpp deleted file mode 100644 index ba99a85..0000000 --- a/WebApplication/WebApplication.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "WebApplication.h" -#include "BoostLog.h" -#include "Database/Session.h" -#include "Hello.h" -#include "Restful.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -WebApplication::WebApplication(uint16_t port, const std::string &documentRoot) { - try { - std::vector 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(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(), "/auth"); - m_server->addResource(std::make_shared(), "/plaintext"); - m_server->addResource(std::make_shared(std::format("{}/database.sqlite", documentRoot)), "/db"); - - m_server->start(); - } catch (const std::exception &e) { - LOG(error) << e.what(); - } -} - -std::unique_ptr WebApplication::createApplication(const Wt::WEnvironment &env, bool embedded) { - return std::make_unique(env, embedded); -} - -WebApplication::~WebApplication() { -} - -void WebApplication::initializeAuthenticationService() { - m_authService = std::make_unique(); - m_authService->setAuthTokensEnabled(true, "logincookie"); - m_passwordService = std::make_unique(*m_authService); - - auto verifier = std::make_unique(); - verifier->addHashFunction(std::make_unique(7)); - m_passwordService->setVerifier(std::move(verifier)); - m_passwordService->setPasswordThrottle(std::make_unique()); - m_passwordService->setStrengthValidator(std::make_unique()); -} - -const Wt::Auth::AuthService &WebApplication::authService() { - return *m_authService; -} - -const Wt::Auth::PasswordService &WebApplication::passwordService() { - return *m_passwordService; -} diff --git a/resources/build.sh b/resources/build.sh index 1a5bb67..8bb3e0f 100755 --- a/resources/build.sh +++ b/resources/build.sh @@ -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