Adding User edit/create view

This commit is contained in:
emeric
2018-04-10 13:24:14 +02:00
parent 05fd8a47d8
commit 2f7697d278
21 changed files with 510 additions and 64 deletions
+13 -14
View File
@@ -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()
{
+2
View File
@@ -34,6 +34,8 @@ class Auth : public Wt::WTemplateFormView
void logout();
private:
void processAuth();
Wt::Auth::AuthModel* _model;
};
+39 -10
View File
@@ -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<Database::Track::pointer> 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
+2 -1
View File
@@ -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;
+12 -3
View File
@@ -156,13 +156,13 @@ class SettingsModel : public Wt::WFormModel
std::size_t bitrate(int row)
{
return boost::any_cast<std::size_t>
(_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<Wt::WString>
(_bitrateModel->data(_encodingModel->index(row, 0), Wt::DisplayRole));
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::DisplayRole));
}
boost::optional<int> 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.
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
}
+258
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WLineEdit>
#include <Wt/WPushButton>
#include <Wt/WTemplateFormView>
#include <Wt/WFormModel>
#include <Wt/WStringListModel>
#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<Database::User::id_type> 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<int> getBitrateLimitRow(Wt::WString value)
{
for (int i = 0; i < _bitrateModel->rowCount(); ++i)
{
if (bitrateLimitString(i) == value)
return i;
}
return boost::none;
}
boost::optional<int> 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<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::UserRole));
}
Wt::WString bitrateLimitString(int row)
{
return boost::any_cast<Wt::WString>
(_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<Database::User::id_type> _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<Database::User::id_type>(*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
+37
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
namespace UserInterface {
class UserView : public Wt::WContainerWidget
{
public:
UserView(Wt::WContainerWidget *parent = 0);
private:
void refresh();
};
} // namespace UserInterface
+37 -10
View File
@@ -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);
}));
}
}