diff --git a/Makefile.am b/Makefile.am index 00bf7d3d..446387c4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,6 +22,7 @@ lms_js_DATA = \ lms_approot_DATA = \ approot/admin-database.xml \ + approot/admin-user.xml \ approot/admin-users.xml \ approot/admin-initwizard.xml \ approot/artist.xml \ diff --git a/approot/admin-user.xml b/approot/admin-user.xml new file mode 100644 index 00000000..f3a9ebc9 --- /dev/null +++ b/approot/admin-user.xml @@ -0,0 +1,57 @@ + + + + + +
+ ${} +
+ +
+ ${login} +
+
+ ${login-info} +
+
+ ${
} +
+ +
+ ${password} +
+
+ ${password-info} +
+
+ +
+ +
+
+ ${audio-bitrate-limit} + kbps +
+
+
+ ${audio-bitrate-limit-info} +
+
+ +
+
+ ${save-btn class="btn-primary"} +
+
+
+
+ +
diff --git a/approot/messages.xml b/approot/messages.xml index b1027651..b7e05584 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -15,6 +15,7 @@ Confirm password Passwords don't match Are you sure? +Save Daily @@ -36,6 +37,11 @@ Edit Users +New user +New user created! +Edit user {1} +User updated! + Administration account created. Please refresh to continue! Create administrator account diff --git a/src/Makefile.am b/src/Makefile.am index bbd38809..d6e43294 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,6 +29,7 @@ lms_SOURCES = \ $(srcdir)/ui/SettingsView.cpp \ $(srcdir)/ui/admin/DatabaseSettingsView.cpp \ $(srcdir)/ui/admin/InitWizardView.cpp \ + $(srcdir)/ui/admin/UserView.cpp \ $(srcdir)/ui/admin/UsersView.cpp \ $(srcdir)/ui/common/InputRange.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 862d6034..2186d2c7 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -164,7 +164,6 @@ Handler::getCurrentUser() User::pointer Handler::getUser(const Wt::Auth::User& authUser) { - if (!authUser.isValid()) { LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser"; return User::pointer(); @@ -172,13 +171,21 @@ Handler::getUser(const Wt::Auth::User& authUser) Wt::Dbo::ptr authInfo = _users->find(authUser); - User::pointer user = authInfo->user(); + return authInfo->user(); +} - if (!user) { - user = _session.add(new User()); - authInfo.modify()->setUser(user); +User::pointer +Handler::createUser(const Wt::Auth::User& authUser) +{ + if (!authUser.isValid()) { + LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser"; + return User::pointer(); } + User::pointer user = _session.add(new User()); + Wt::Dbo::ptr authInfo = _users->find(authUser); + authInfo.modify()->setUser(user); + return user; } diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp index 28f5600a..f99aa870 100644 --- a/src/database/DatabaseHandler.hpp +++ b/src/database/DatabaseHandler.hpp @@ -48,7 +48,8 @@ class Handler Wt::Dbo::Session& getSession() { return _session; } Wt::Dbo::ptr getCurrentUser(); // get the current user, may return empty - Wt::Dbo::ptr getUser(const Wt::Auth::User& authUser); // Get or create the given user + Wt::Dbo::ptr getUser(const Wt::Auth::User& authUser); + Wt::Dbo::ptr createUser(const Wt::Auth::User& authUser); Wt::Auth::AbstractUserDatabase& getUserDatabase(); Wt::Auth::Login& getLogin() { return _login; } diff --git a/src/database/User.cpp b/src/database/User.cpp index c299afde..61584657 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -32,7 +32,7 @@ User::audioBitrates = }; User::User() -: _maxAudioBitrate(maxAudioBitrate), +: _maxAudioBitrate(audioBitrates.back()), _isAdmin(false), _audioBitrate(defaultAudioBitrate), _audioEncoding(AudioEncoding::AUTO), @@ -63,31 +63,27 @@ User::getById(Wt::Dbo::Session& session, id_type id) void User::setAudioBitrate(std::size_t bitrate) { - _audioBitrate = std::min(bitrate, std::min(static_cast(_maxAudioBitrate), audioBitrates.back())); + _audioBitrate = std::min(bitrate, static_cast(_maxAudioBitrate)); } void User::setMaxAudioBitrate(std::size_t bitrate) { - _maxAudioBitrate = std::min(bitrate, static_cast(_maxAudioBitrate)); + _maxAudioBitrate = std::min(bitrate, audioBitrates.back()); + if (_audioBitrate > _maxAudioBitrate) + _audioBitrate = _maxAudioBitrate; } std::size_t User::getAudioBitrate(void) const { - if (!isAdmin()) - return std::min(static_cast(_audioBitrate), std::min(static_cast(_maxAudioBitrate), audioBitrates.back())); - else - return std::min(static_cast(_audioBitrate), audioBitrates.back()); + return _audioBitrate; } std::size_t User::getMaxAudioBitrate(void) const { - if (!isAdmin()) - return std::min(static_cast(_maxAudioBitrate), audioBitrates.back()); - else - return audioBitrates.back(); + return _maxAudioBitrate; } } // namespace Database diff --git a/src/database/User.hpp b/src/database/User.hpp index 35ce625e..d9c1923e 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -41,7 +41,7 @@ enum class AudioEncoding WEBMA, }; -class User +class User : public Wt::Dbo::Dbo { public: typedef Wt::Dbo::dbo_traits::IdType id_type; @@ -70,9 +70,9 @@ class User void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } // read - bool isAdmin() const {return _isAdmin;} + bool isAdmin() const { return _isAdmin; } std::size_t getAudioBitrate() const; - AudioEncoding getAudioEncoding() const { return _audioEncoding;} + AudioEncoding getAudioEncoding() const { return _audioEncoding; } std::size_t getMaxAudioBitrate() const; std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } @@ -90,7 +90,6 @@ class User private: - static const std::size_t maxAudioBitrate = 320000; static const std::size_t defaultAudioBitrate = 128000; // Admin defined settings diff --git a/src/main/main.cpp b/src/main/main.cpp index 4715640c..0abbc96c 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -30,8 +30,6 @@ #include "feature/FeatureExtractor.hpp" #include "scanner/MediaScanner.hpp" -//#include "database/updater/DatabaseFeatureExtractor.hpp" -//#include "database/updater/DatabaseHighLevelCluster.hpp" #include "ui/LmsApplication.hpp" diff --git a/src/ui/Auth.cpp b/src/ui/Auth.cpp index bf242a64..5713d065 100644 --- a/src/ui/Auth.cpp +++ b/src/ui/Auth.cpp @@ -57,20 +57,8 @@ Auth::Auth(Wt::WContainerWidget *parent) auto loginBtn = new Wt::WPushButton(Wt::WString::tr("Lms.login")); bindWidget("login-btn", loginBtn); - loginBtn->clicked().connect(std::bind([=] - { - updateModel(_model); - - if (_model->validate()) - _model->login(DbHandler().getLogin()); - else - updateView(_model); - })); - - password->enterPressed().connect(std::bind([=] - { - loginBtn->clicked().emit(Wt::WMouseEvent()); - })); + loginBtn->clicked().connect(this, &Auth::processAuth); + password->enterPressed().connect(this, &Auth::processAuth); DbHandler().getLogin().changed().connect(std::bind([=] { @@ -84,6 +72,17 @@ Auth::Auth(Wt::WContainerWidget *parent) updateView(_model); } +void +Auth::processAuth() +{ + updateModel(_model); + + if (_model->validate()) + _model->login(DbHandler().getLogin()); + else + updateView(_model); +} + void Auth::logout() { diff --git a/src/ui/Auth.hpp b/src/ui/Auth.hpp index 37e09eff..65eec670 100644 --- a/src/ui/Auth.hpp +++ b/src/ui/Auth.hpp @@ -34,6 +34,8 @@ class Auth : public Wt::WTemplateFormView void logout(); private: + void processAuth(); + Wt::Auth::AuthModel* _model; }; diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 87b4220e..7eec6f1d 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -39,6 +39,7 @@ #include "admin/InitWizardView.hpp" #include "admin/DatabaseSettingsView.hpp" +#include "admin/UserView.hpp" #include "admin/UsersView.hpp" #include "resource/ImageResource.hpp" @@ -87,6 +88,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti // Add a resource bundle messageResourceBundle().use(appRoot() + "admin-database"); + messageResourceBundle().use(appRoot() + "admin-user"); messageResourceBundle().use(appRoot() + "admin-users"); messageResourceBundle().use(appRoot() + "admin-wizard"); messageResourceBundle().use(appRoot() + "artist"); @@ -103,6 +105,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti messageResourceBundle().use(appRoot() + "tracks"); messageResourceBundle().use(appRoot() + "templates"); + // Require js here to avoid async problems + require("/js/mediaplayer.js"); + setTitle("LMS"); // If here is no account in the database, launch the first connection wizard @@ -121,7 +126,18 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti } else { - DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); + DbHandler().getLogin().changed().connect(std::bind([=] + { + try + { + handleAuthEvent(); + } + catch (std::exception& e) + { + LMS_LOG(UI, ERROR) << "Error while handling auth event: " << e.what(); + throw std::runtime_error("Internal error"); + } + })); _auth = new Auth(); root()->addWidget(_auth); @@ -199,6 +215,7 @@ LmsApplication::goHome() void LmsApplication::quit() { + setConfirmCloseMessage(""); WApplication::quit(""); redirect("/"); } @@ -211,6 +228,7 @@ enum IdxRoot IdxSettings, IdxAdminDatabase, IdxAdminUsers, + IdxAdminUser, }; static void @@ -233,6 +251,7 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin) { "/settings", IdxSettings, false }, { "/admin/database", IdxAdminDatabase, true }, { "/admin/users", IdxAdminUsers, true }, + { "/admin/user", IdxAdminUser, true }, }; LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; @@ -270,8 +289,6 @@ LmsApplication::handleAuthEvent(void) _isAdmin = CurrentUser()->isAdmin(); } - require("/js/mediaplayer.js"); - _imageResource = new ImageResource(_db, root()); _transcodeResource = new TranscodeResource(_db, root()); @@ -368,6 +385,9 @@ LmsApplication::handleAuthEvent(void) auto users = new UsersView(); mainStack->addWidget(users); + + auto user = new UserView(); + mainStack->addWidget(user); } explore->tracksAdd.connect(std::bind([=] (std::vector tracks) @@ -411,7 +431,7 @@ LmsApplication::handleAuthEvent(void) { Wt::WServer::instance()->post(sessionId, [=] { - notify(Wt::WString::tr("Lms.Admin.Database.scan-complete") + notifyMsg(Wt::WString::tr("Lms.Admin.Database.scan-complete") .arg(stats.nbFiles()) .arg(stats.additions) .arg(stats.deletions) @@ -431,17 +451,26 @@ LmsApplication::handleAuthEvent(void) } void -LmsApplication::notify(const Wt::WString& message) +LmsApplication::notify(const Wt::WEvent& event) +{ + try + { + WApplication::notify(event); + } + catch (std::exception& e) + { + LMS_LOG(UI, ERROR) << "Caught exception: " << e.what(); + throw std::runtime_error("Internal error"); + } +} + +void +LmsApplication::notifyMsg(const Wt::WString& message) { LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "'"; root()->addWidget(new Wt::WText(message)); } -void notify(const Wt::WString& message) -{ - LmsApplication::instance()->notify(message); -} - } // namespace UserInterface diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index 8ff8adbc..b59ef635 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -49,7 +49,7 @@ class LmsApplication : public Wt::WApplication // Utils void goHome(); void quit(); - void notify(const Wt::WString& message); + void notifyMsg(const Wt::WString& message); static Wt::WAnchor* createArtistAnchor(Database::Artist::pointer artist, bool addText = true); static Wt::WAnchor* createReleaseAnchor(Database::Release::pointer release, bool addText = true); @@ -59,6 +59,7 @@ class LmsApplication : public Wt::WApplication LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner); void handleAuthEvent(void); + void notify(const Wt::WEvent& event) override; Database::Handler _db; Auth* _auth; diff --git a/src/ui/SettingsView.cpp b/src/ui/SettingsView.cpp index 7b1ba7c4..0daa1295 100644 --- a/src/ui/SettingsView.cpp +++ b/src/ui/SettingsView.cpp @@ -156,13 +156,13 @@ class SettingsModel : public Wt::WFormModel std::size_t bitrate(int row) { return boost::any_cast - (_bitrateModel->data(_encodingModel->index(row, 0), Wt::UserRole)); + (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::UserRole)); } Wt::WString bitrateString(int row) { return boost::any_cast - (_bitrateModel->data(_encodingModel->index(row, 0), Wt::DisplayRole)); + (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::DisplayRole)); } boost::optional getEncodingRow(Wt::WString value) @@ -207,9 +207,18 @@ class SettingsModel : public Wt::WFormModel _bitrateModel = new Wt::WStringListModel(this); + std::size_t maxAudioBitrate; + { + Wt::Dbo::Transaction transaction(DboSession()); + maxAudioBitrate = CurrentUser()->getMaxAudioBitrate(); + } + std::size_t id = 0; for (auto bitrate : Database::User::audioBitrates) { + if (bitrate > maxAudioBitrate) + break; + _bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) ); _bitrateModel->setData( id++, 0, bitrate, Wt::UserRole); } @@ -282,7 +291,7 @@ SettingsView::SettingsView(Wt::WContainerWidget *parent) if (model->validate()) { model->saveData(); - LmsApp->notify(Wt::WString::tr("Lms.Settings.settings-saved")); + LmsApp->notifyMsg(Wt::WString::tr("Lms.Settings.settings-saved")); } // Udate the view: Delete any validation message in the view, etc. diff --git a/src/ui/admin/DatabaseSettingsView.cpp b/src/ui/admin/DatabaseSettingsView.cpp index b5b0b507..9373dd59 100644 --- a/src/ui/admin/DatabaseSettingsView.cpp +++ b/src/ui/admin/DatabaseSettingsView.cpp @@ -267,7 +267,7 @@ DatabaseSettingsView::DatabaseSettingsView(Wt::WContainerWidget *parent) model->saveData(); LmsApp->getMediaScanner().reschedule(); - LmsApp->notify(Wt::WString::tr("Lms.Admin.Database.settings-saved")); + LmsApp->notifyMsg(Wt::WString::tr("Lms.Admin.Database.settings-saved")); } // Udate the view: Delete any validation message in the view, etc. @@ -284,7 +284,7 @@ DatabaseSettingsView::DatabaseSettingsView(Wt::WContainerWidget *parent) immScanBtn->clicked().connect(std::bind([=] () { LmsApp->getMediaScanner().scheduleImmediateScan(); - LmsApp->notify(Wt::WString::tr("Lms.Admin.Database.scan-launched")); + LmsApp->notifyMsg(Wt::WString::tr("Lms.Admin.Database.scan-launched")); })); updateView(model); diff --git a/src/ui/admin/InitWizardView.cpp b/src/ui/admin/InitWizardView.cpp index 4acb3965..79608064 100644 --- a/src/ui/admin/InitWizardView.cpp +++ b/src/ui/admin/InitWizardView.cpp @@ -148,7 +148,7 @@ InitWizardView::InitWizardView(Wt::WContainerWidget *parent) if (model->validate()) { model->saveData(); - LmsApp->notify(Wt::WString::tr("Lms.Admin.InitWizard.done")); + LmsApp->notifyMsg(Wt::WString::tr("Lms.Admin.InitWizard.done")); saveButton->setEnabled(false); } diff --git a/src/ui/admin/UserView.cpp b/src/ui/admin/UserView.cpp new file mode 100644 index 00000000..3d466b5e --- /dev/null +++ b/src/ui/admin/UserView.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2018 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/Validators.hpp" +#include "utils/Utils.hpp" +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" +#include "UserView.hpp" + +namespace UserInterface { + + +class UserModel : public Wt::WFormModel +{ + public: + static const Field LoginField; + static const Field PasswordField; + static const Field BitrateLimitField; + + UserModel(boost::optional userId, Wt::WObject *parent = 0) + : Wt::WFormModel(parent), + _userId(userId) + { + if (!_userId) + { + addField(LoginField); + setValidator(LoginField, createNameValidator()); + } + + addField(PasswordField); + addField(BitrateLimitField); + + + if (!_userId) + setValidator(PasswordField, createMandatoryValidator()); + + initializeModels(); + + // populate the model with initial data + loadData(); + } + + void loadData() + { + if (!_userId) + return; + + Wt::Dbo::Transaction transaction(DboSession()); + + Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*_userId) ); + Database::User::pointer user = DbHandler().getUser(authUser); + + if (user == CurrentUser()) + throw std::runtime_error("Cannot edit ourselves"); + + auto bitrate = getBitrateLimitRow(user->getMaxAudioBitrate()); + if (bitrate) + setValue(BitrateLimitField, bitrateLimitString(*bitrate)); + } + + void saveData() + { + Wt::Dbo::Transaction transaction(DboSession()); + + if (_userId) + { + // Update user + Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*_userId) ); + Database::User::pointer user = DbHandler().getUser( authUser ); + + // Account + if (!valueText(PasswordField).empty()) + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + auto bitrateLimitRow = getBitrateLimitRow(Wt::asString(value(BitrateLimitField))); + user.modify()->setMaxAudioBitrate(bitrateLimit(*bitrateLimitRow)); + LMS_LOG(UI, DEBUG) << "Max audio bitrate set to " << bitrateLimit(*bitrateLimitRow); + } + else + { + // Create user + Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); + Database::User::pointer user = DbHandler().createUser(authUser); + + // Account + authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField)); + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + auto bitrateLimitRow = getBitrateLimitRow(Wt::asString(value(BitrateLimitField))); + user.modify()->setMaxAudioBitrate(bitrateLimit(*bitrateLimitRow)); + } + } + + boost::optional getBitrateLimitRow(Wt::WString value) + { + for (int i = 0; i < _bitrateModel->rowCount(); ++i) + { + if (bitrateLimitString(i) == value) + return i; + } + + return boost::none; + } + + boost::optional getBitrateLimitRow(std::size_t value) + { + for (int i = 0; i < _bitrateModel->rowCount(); ++i) + { + if (bitrateLimit(i) == value) + return i; + } + + return boost::none; + } + + std::size_t bitrateLimit(int row) + { + return boost::any_cast + (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::UserRole)); + } + + Wt::WString bitrateLimitString(int row) + { + return boost::any_cast + (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::DisplayRole)); + } + + Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; } + + private: + + void initializeModels() + { + _bitrateModel = new Wt::WStringListModel(this); + + std::size_t id = 0; + for (auto bitrate : Database::User::audioBitrates) + { + _bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) ); + _bitrateModel->setData( id++, 0, bitrate, Wt::UserRole); + } + + + } + + Wt::WStringListModel* _bitrateModel; + boost::optional _userId; +}; + +const Wt::WFormModel::Field UserModel::LoginField = "login"; +const Wt::WFormModel::Field UserModel::PasswordField = "password"; +const Wt::WFormModel::Field UserModel::BitrateLimitField = "audio-bitrate-limit"; + +UserView::UserView(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + + refresh(); +} + +void +UserView::refresh() +{ + if (!wApp->internalPathMatches("/admin/user")) + return; + + auto userId = readLong(wApp->internalPathNextPart("/admin/user/")); + + clear(); + + auto t = new Wt::WTemplateFormView(Wt::WString::tr("Lms.Admin.User.template"), this); + + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + t->addFunction("id", &Wt::WTemplate::Functions::id); + + auto model = new UserModel(userId ? boost::make_optional(*userId) : boost::none, this); + + if (userId) + { + auto authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*userId) ); + auto name = authUser.identity(Wt::Auth::Identity::LoginName); + t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::PlainText); + } + else + { + // Login + t->setCondition("if-has-login", true); + t->setFormWidget(UserModel::LoginField, new Wt::WLineEdit()); + t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-create")); + } + + + // Password + Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); + t->setFormWidget(UserModel::PasswordField, passwordEdit ); + passwordEdit->setEchoMode(Wt::WLineEdit::Password); + + // AudioBitrate + Wt::WComboBox *bitrateCB = new Wt::WComboBox(); + t->setFormWidget(UserModel::BitrateLimitField, bitrateCB); + bitrateCB->setModel(model->bitrateModel()); + + auto saveBtn = new Wt::WPushButton(Wt::WString::tr(userId ? "Lms.save" : "Lms.create")); + t->bindWidget("save-btn", saveBtn); + saveBtn->clicked().connect(std::bind([=] + { + t->updateModel(model); + + if (model->validate()) + { + model->saveData(); + LmsApp->notifyMsg(Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); + LmsApp->setInternalPath("/admin/users", true); + } + else + { + t->updateView(model); + } + })); + + t->updateView(model); + +} + +} // namespace UserInterface + + diff --git a/src/ui/admin/UserView.hpp b/src/ui/admin/UserView.hpp new file mode 100644 index 00000000..723c0f13 --- /dev/null +++ b/src/ui/admin/UserView.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +namespace UserInterface { + +class UserView : public Wt::WContainerWidget +{ + public: + UserView(Wt::WContainerWidget *parent = 0); + + private: + void refresh(); +}; + +} // namespace UserInterface + + diff --git a/src/ui/admin/UsersView.cpp b/src/ui/admin/UsersView.cpp index c5ecb4fc..6c0a4153 100644 --- a/src/ui/admin/UsersView.cpp +++ b/src/ui/admin/UsersView.cpp @@ -31,7 +31,6 @@ namespace UserInterface { UsersView::UsersView(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) { - LMS_LOG(UI, DEBUG) << "TEST"; auto t = new Wt::WTemplate(Wt::WString::tr("Lms.Admin.Users.template"), this); t->addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -41,12 +40,25 @@ UsersView::UsersView(Wt::WContainerWidget *parent) auto addBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.add")); t->bindWidget("add-btn", addBtn); + addBtn->clicked().connect(std::bind([=] + { + LmsApp->setInternalPath("/admin/user", true); + })); + + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + refresh(); } void UsersView::refresh() { + if (!wApp->internalPathMatches("/admin/users")) + return; + _container->clear(); Wt::Dbo::Transaction transaction(DboSession()); @@ -54,9 +66,10 @@ UsersView::refresh() auto users = Database::User::getAll(DboSession()); for (auto user : users) { + auto userId = std::to_string(user.id()); auto entry = new Wt::WTemplate(Wt::WString::tr("Lms.Admin.Users.template.entry"), _container); - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(std::to_string(user.id())); + Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(userId); if (!authUser.isValid()) { LMS_LOG(UI, ERROR) << "Skipping invalid userId = " << user.id(); @@ -66,15 +79,29 @@ UsersView::refresh() entry->bindString("name", authUser.identity(Wt::Auth::Identity::LoginName), Wt::PlainText); // Don't edit ourself this way - if (CurrentUser() != user) - { - entry->setCondition("if-edit", true); - auto editBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.edit")); - entry->bindWidget("edit-btn", editBtn); + if (CurrentUser() == user) + continue; - auto delBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.del")); - entry->bindWidget("del-btn", delBtn); - } + entry->setCondition("if-edit", true); + auto editBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.edit")); + entry->bindWidget("edit-btn", editBtn); + editBtn->clicked().connect(std::bind([=] + { + LmsApp->setInternalPath("/admin/user/" + std::to_string(user.id()), true); + })); + + auto delBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.del")); + entry->bindWidget("del-btn", delBtn); + delBtn->clicked().connect(std::bind([=] + { + Wt::Dbo::Transaction transaction(DboSession()); + + auto authUser = DbHandler().getUserDatabase().findWithId(userId); + auto user = DbHandler().getUser(authUser); + DbHandler().getUserDatabase().deleteUser( authUser ); + user.remove(); + _container->removeWidget(entry); + })); } } diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 8b71586a..67117b20 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -27,6 +27,19 @@ #include "Utils.hpp" +boost::optional +readLong(const std::string& str) +{ + try + { + return std::stol(str); + } + catch (std::exception& e) + { + return boost::none; + } +} + bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time) { const std::locale formats[] = { diff --git a/src/utils/Utils.hpp b/src/utils/Utils.hpp index a7e89054..905297c4 100644 --- a/src/utils/Utils.hpp +++ b/src/utils/Utils.hpp @@ -24,8 +24,13 @@ #include #include +#include #include + +boost::optional +readLong(const std::string& str); + bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time);