WIP. Added User form
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
[Users]
|
||||
- Handle login lifetime?
|
||||
- Disable multi login?
|
||||
- Limit multi login from UI and remote interface (limit per interface is OK)
|
||||
|
||||
[Cover]
|
||||
- Scaling: find something more "reliable" than GIL and its customs extensions (adobe work, io_new)?
|
||||
@@ -25,15 +25,20 @@
|
||||
- Use size limits for strings (artist, release, genre)?
|
||||
- Process only files whose extensions are well known in audio/video world (avoid useless parsing/errors)
|
||||
|
||||
[Transcode]
|
||||
- some zombies seem to be remaining after a week of use
|
||||
|
||||
[UI]
|
||||
[Settings]
|
||||
- logout users that are being changed (loss of admin admin rights), or make sure they are still admin when they make changes
|
||||
- "signal not exposed" problem if a user logout and login again. bad resource destruction?
|
||||
[admin/Users]
|
||||
- Add/remove users
|
||||
- max audio/video bitrates for user
|
||||
- capabilities (admin)
|
||||
[admin/DB]
|
||||
- Add path to Audio/Video files
|
||||
- Do not make the update start time active when update perdiod is "Never"
|
||||
- Add a dedicated Menu to add/delete and view the Media pathes (see Users for the idea)
|
||||
- Do not make the update start time field active when update perdiod is "Never"
|
||||
- Do not restart the db update service if user applied no changes
|
||||
- Uncheck the "Request immediate scan" once setins are applied
|
||||
[user/transcoding]
|
||||
@@ -47,7 +52,7 @@
|
||||
- TrackView : handle original release date
|
||||
- TrackView : handle duration > 1 hour
|
||||
- TrackView : select only relevant columns to speed up queries (do not get eveything)
|
||||
- TrackView : Reselect the current playing song when displaying the updated search results
|
||||
- TrackView : Reselect the current selected item when displaying the updated search results
|
||||
- ReleaseView: display the release's publication year
|
||||
- OGG metadata -> properly handle metadata nested in the audio stream
|
||||
- TrackView : when udating the view, reselect the current playing track
|
||||
|
||||
@@ -103,6 +103,12 @@ Handler::getCurrentUser()
|
||||
User::pointer
|
||||
Handler::getUser(const Wt::Auth::User& authUser)
|
||||
{
|
||||
|
||||
if (!authUser.isValid()) {
|
||||
std::cerr << "Handler::getUser: invalid authUser" << std::endl;
|
||||
return User::pointer();
|
||||
}
|
||||
|
||||
Wt::Dbo::ptr<AuthInfo> authInfo = _users->find(authUser);
|
||||
|
||||
User::pointer user = authInfo->user();
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Database {
|
||||
|
||||
typedef Wt::Auth::Dbo::UserDatabase<AuthInfo> UserDatabase;
|
||||
|
||||
// Session living class handling the database
|
||||
// Session living class handling the database and the login
|
||||
class Handler
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -21,6 +21,11 @@ User::getAll(Wt::Dbo::Session& session)
|
||||
return std::vector<pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::getById(Wt::Dbo::Session& session, std::string id)
|
||||
{
|
||||
return session.find<User>().where("id = ?").bind( id );
|
||||
}
|
||||
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -12,15 +12,25 @@ typedef Wt::Auth::Dbo::AuthInfo<User> AuthInfo;
|
||||
class User {
|
||||
|
||||
public:
|
||||
static const std::size_t MaxNameLength = 15;
|
||||
|
||||
User();
|
||||
|
||||
typedef Wt::Dbo::ptr<User> pointer;
|
||||
|
||||
// accessors
|
||||
static pointer getById(Wt::Dbo::Session& session, std::string id);
|
||||
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
|
||||
|
||||
// write
|
||||
void setAdmin(bool admin) { _isAdmin = admin; }
|
||||
void setMaxAudioBitrate(std::size_t bitrate) { _maxAudioBitrate = bitrate; }
|
||||
void setMaxVideoBitrate(std::size_t bitrate) { _maxVideoBitrate = bitrate; }
|
||||
|
||||
// read
|
||||
bool isAdmin() const {return _isAdmin;}
|
||||
std::size_t getMaxAudioBitrate() const { return _maxAudioBitrate; }
|
||||
std::size_t getMaxVideoBitrate() const { return _maxVideoBitrate; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
|
||||
@@ -2,6 +2,104 @@
|
||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||
<!--FORMS message blocks-->
|
||||
|
||||
<message id="userForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<legend>Access</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:admin}">
|
||||
Admin
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${admin}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${admin-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:audio-bitrate-limit}">
|
||||
Audio Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${audio-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${audio-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:video-bitrate-limit}">
|
||||
Video Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${video-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${video-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</message>
|
||||
|
||||
<message id="databaseForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <Wt/WRegExpValidator>
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
static inline Wt::WValidator *createEmailValidator()
|
||||
{
|
||||
return new Wt::WRegExpValidator("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}");
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
@@ -40,10 +40,10 @@ class DatabaseFormModel : public Wt::WFormModel
|
||||
addField(UpdateStartTimeField);
|
||||
addField(UpdateRequestImmediateField);
|
||||
|
||||
setValidator(PathField, createPathValidator(PathField));
|
||||
setValidator(UpdatePeriodField, createUpdatePeriodValidator(UpdatePeriodField));
|
||||
setValidator(UpdateStartTimeField, createStartTimeValidator(UpdateStartTimeField));
|
||||
setValidator(UpdateRequestImmediateField, createRequestImmediateFieldValidator(UpdateRequestImmediateField));
|
||||
setValidator(PathField, createPathValidator());
|
||||
setValidator(UpdatePeriodField, createUpdatePeriodValidator());
|
||||
setValidator(UpdateStartTimeField, createStartTimeValidator());
|
||||
setValidator(UpdateRequestImmediateField, createRequestImmediateFieldValidator());
|
||||
|
||||
// populate the model with initial data
|
||||
loadData();
|
||||
@@ -206,25 +206,25 @@ class DatabaseFormModel : public Wt::WFormModel
|
||||
|
||||
}
|
||||
|
||||
Wt::WValidator *createPathValidator(const std::string& field) {
|
||||
Wt::WValidator *createPathValidator() {
|
||||
DirectoryValidator* v = new DirectoryValidator();
|
||||
v->setMandatory(true);
|
||||
return v;
|
||||
}
|
||||
|
||||
Wt::WValidator *createUpdatePeriodValidator(const std::string& field) {
|
||||
Wt::WValidator *createUpdatePeriodValidator() {
|
||||
Wt::WValidator* v = new Wt::WValidator();
|
||||
v->setMandatory(true);
|
||||
return v;
|
||||
}
|
||||
|
||||
Wt::WValidator *createStartTimeValidator(const std::string& field) {
|
||||
Wt::WValidator *createStartTimeValidator() {
|
||||
Wt::WValidator* v = new Wt::WValidator();
|
||||
v->setMandatory(true);
|
||||
return v;
|
||||
}
|
||||
|
||||
Wt::WValidator *createRequestImmediateFieldValidator(const std::string& field) {
|
||||
Wt::WValidator *createRequestImmediateFieldValidator() {
|
||||
Wt::WValidator* v = new Wt::WValidator();
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class DatabaseFormView : public Wt::WTemplateFormView
|
||||
DatabaseFormView(SessionData& sessionData, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
private:
|
||||
|
||||
void processSave();
|
||||
void processDiscard();
|
||||
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
#include <vector>
|
||||
|
||||
#include <Wt/WStringListModel>
|
||||
#include <Wt/WFormModel>
|
||||
#include <Wt/WLengthValidator>
|
||||
#include <Wt/WLineEdit>
|
||||
#include <Wt/WCheckBox>
|
||||
#include <Wt/WComboBox>
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/Auth/Identity>
|
||||
#include <Wt/Auth/PasswordStrengthValidator>
|
||||
#include <Wt/Auth/AbstractPasswordService>
|
||||
#include <Wt/Auth/PasswordVerifier>
|
||||
|
||||
#include "common/EmailValidator.hpp"
|
||||
|
||||
#include "SettingsUserFormView.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
namespace Settings {
|
||||
|
||||
class UserFormModel : public Wt::WFormModel
|
||||
{
|
||||
public:
|
||||
|
||||
// Associate each field with a unique string literal.
|
||||
static const Field NameField;
|
||||
static const Field EmailField;
|
||||
static const Field PasswordField;
|
||||
static const Field PasswordConfirmField;
|
||||
static const Field AdminField;
|
||||
static const Field AudioBitrateLimitField;
|
||||
static const Field VideoBitrateLimitField;
|
||||
|
||||
UserFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0)
|
||||
: Wt::WFormModel(parent),
|
||||
_sessionData(sessionData),
|
||||
_userId(userId)
|
||||
{
|
||||
initializeModels();
|
||||
|
||||
addField(NameField);
|
||||
addField(EmailField);
|
||||
addField(PasswordField);
|
||||
addField(PasswordConfirmField);
|
||||
addField(AdminField);
|
||||
addField(AudioBitrateLimitField);
|
||||
addField(VideoBitrateLimitField);
|
||||
|
||||
setValidator(NameField, createNameValidator());
|
||||
Wt::WValidator *emailValidator = createEmailValidator();
|
||||
emailValidator->setMandatory(true);
|
||||
setValidator(EmailField, emailValidator);
|
||||
// If creating a user, passwords are mandatory
|
||||
if (_userId.empty())
|
||||
{
|
||||
setValidator(PasswordField, new Wt::WValidator(true)); // mandatory
|
||||
setValidator(PasswordConfirmField, new Wt::WValidator(true)); // mandatory
|
||||
}
|
||||
setValidator(AudioBitrateLimitField, new Wt::WValidator(true)); // mandatory
|
||||
setValidator(VideoBitrateLimitField, new Wt::WValidator(true)); // mandatory
|
||||
|
||||
// populate the model with initial data
|
||||
loadData(userId);
|
||||
}
|
||||
|
||||
Wt::WAbstractItemModel *audioBitrateModel() { return _audioBitrateModel; }
|
||||
Wt::WAbstractItemModel *videoBitrateModel() { return _videoBitrateModel; }
|
||||
|
||||
void loadData(std::string userId)
|
||||
{
|
||||
|
||||
if (!userId.empty())
|
||||
{
|
||||
Database::Handler& db = _sessionData.getDatabaseHandler();
|
||||
|
||||
Wt::Dbo::Transaction transaction(db.getSession());
|
||||
|
||||
Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId );
|
||||
Database::User::pointer user = db.getUser(authUser);
|
||||
|
||||
Wt::Auth::User currentUser = _sessionData.getDatabaseHandler().getLogin().user();
|
||||
|
||||
if (user && user->isAdmin())
|
||||
{
|
||||
setValue(AdminField, true);
|
||||
|
||||
// We can cannot remove admin rights to ourselves
|
||||
if (currentUser == authUser)
|
||||
setReadOnly(AdminField, true);
|
||||
|
||||
// if the user is admin, no need to limit it
|
||||
setReadOnly(AudioBitrateLimitField, true);
|
||||
setValidator(AudioBitrateLimitField, nullptr);
|
||||
|
||||
setReadOnly(VideoBitrateLimitField, true);
|
||||
setValidator(VideoBitrateLimitField, nullptr);
|
||||
}
|
||||
|
||||
|
||||
if (user && authUser.isValid())
|
||||
{
|
||||
setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName));
|
||||
if (!authUser.email().empty())
|
||||
setValue(EmailField, authUser.email());
|
||||
else
|
||||
setValue(EmailField, authUser.unverifiedEmail());
|
||||
}
|
||||
|
||||
if (user)
|
||||
{
|
||||
setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps
|
||||
setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool saveData()
|
||||
{
|
||||
// DBO transaction active here
|
||||
try {
|
||||
Database::Handler& db = _sessionData.getDatabaseHandler();
|
||||
Wt::Dbo::Transaction transaction(db.getSession());
|
||||
|
||||
if (_userId.empty())
|
||||
{
|
||||
// Create user
|
||||
Wt::Auth::User authUser = db.getUserDatabase().registerNew();
|
||||
Database::User::pointer user = db.getUser(authUser);
|
||||
|
||||
// Account
|
||||
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField));
|
||||
authUser.setEmail(valueText(EmailField).toUTF8());
|
||||
db.getPasswordService().updatePassword(authUser, valueText(PasswordField));
|
||||
|
||||
// Access
|
||||
{
|
||||
boost::any v = value(AdminField);
|
||||
if (!v.empty() && boost::any_cast<bool>(v) == true)
|
||||
user.modify()->setAdmin( true );
|
||||
else
|
||||
user.modify()->setAdmin( false );
|
||||
}
|
||||
|
||||
if (!isReadOnly(AudioBitrateLimitField))
|
||||
user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps
|
||||
|
||||
if (!isReadOnly(VideoBitrateLimitField))
|
||||
user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Update user
|
||||
Wt::Auth::User authUser = db.getUserDatabase().findWithId(_userId);
|
||||
Database::User::pointer user = db.getUser( authUser );
|
||||
|
||||
// user may have been deleted by someone else
|
||||
if (!authUser.isValid()) {
|
||||
std::cerr << "user identity does not exist!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
else if(!user)
|
||||
{
|
||||
std::cerr << "User not found!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Account
|
||||
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField));
|
||||
authUser.setEmail(valueText(EmailField).toUTF8());
|
||||
|
||||
// Password
|
||||
if (!valueText(PasswordField).empty())
|
||||
db.getPasswordService().updatePassword(authUser, valueText(PasswordField));
|
||||
|
||||
// Access
|
||||
if (!isReadOnly(AdminField))
|
||||
{
|
||||
boost::any v = value(AdminField);
|
||||
if (!v.empty() && boost::any_cast<bool>(v) == true)
|
||||
user.modify()->setAdmin( true );
|
||||
else
|
||||
user.modify()->setAdmin( false );
|
||||
}
|
||||
|
||||
if (!isReadOnly(AudioBitrateLimitField))
|
||||
user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps
|
||||
|
||||
if (!isReadOnly(VideoBitrateLimitField))
|
||||
user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
std::cerr << "Dbo exception: " << exception.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateField(Field field)
|
||||
{
|
||||
// DBO transaction active here
|
||||
|
||||
Wt::WString error;
|
||||
|
||||
if (field == NameField)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
|
||||
// Must be unique since used as LoginIdentity
|
||||
Wt::Auth::User user = _sessionData.getDatabaseHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field));
|
||||
if (user.isValid() && user.id() != _userId)
|
||||
error = "Already exists";
|
||||
else
|
||||
return Wt::WFormModel::validateField(field);
|
||||
}
|
||||
else if (field == PasswordField)
|
||||
{
|
||||
// Password is mandatory if we create the user
|
||||
if (!valueText(PasswordField).empty())
|
||||
{
|
||||
// Evaluate the strength of the password
|
||||
Wt::Auth::PasswordStrengthValidator validator;
|
||||
|
||||
// Reduce some constraints...
|
||||
validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 11);
|
||||
validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 8 );
|
||||
validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 6 );
|
||||
|
||||
Wt::Auth::AbstractPasswordService::StrengthValidatorResult res
|
||||
= validator.evaluateStrength(valueText(PasswordField),
|
||||
valueText(NameField),
|
||||
valueText(EmailField).toUTF8());
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
}
|
||||
else
|
||||
return Wt::WFormModel::validateField(field);
|
||||
}
|
||||
else if (field == PasswordConfirmField)
|
||||
{
|
||||
if (validation(PasswordField).state() == Wt::WValidator::Valid)
|
||||
{
|
||||
if (valueText(PasswordField) != valueText(PasswordConfirmField))
|
||||
error = Wt::WString::tr("Wt.Auth.passwords-dont-match");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply validators
|
||||
return Wt::WFormModel::validateField(field);
|
||||
}
|
||||
|
||||
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error));
|
||||
|
||||
return validation(field).state() == Wt::WValidator::Valid;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
static Wt::WValidator *createNameValidator() {
|
||||
Wt::WLengthValidator *v = new Wt::WLengthValidator();
|
||||
v->setMandatory(true);
|
||||
v->setMinimumLength(3);
|
||||
v->setMaximumLength(::Database::User::MaxNameLength);
|
||||
return v;
|
||||
}
|
||||
|
||||
void initializeModels()
|
||||
{
|
||||
|
||||
// AUDIO
|
||||
// TODO move defaults somewhere else?
|
||||
static const std::vector<std::size_t>
|
||||
audioBitrateLimits =
|
||||
{
|
||||
64,
|
||||
96,
|
||||
128,
|
||||
160,
|
||||
192,
|
||||
224,
|
||||
256,
|
||||
320,
|
||||
512
|
||||
};
|
||||
|
||||
_audioBitrateModel = new Wt::WStringListModel();
|
||||
for (std::size_t i = 0; i < audioBitrateLimits.size(); ++i)
|
||||
_audioBitrateModel->addString( Wt::WString("{1}").arg( audioBitrateLimits[i] ) );
|
||||
|
||||
|
||||
// VIDEO
|
||||
// TODO move defaults somewhere else?
|
||||
static const std::vector<std::size_t>
|
||||
videoBitrateLimits =
|
||||
{
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
2048,
|
||||
4096,
|
||||
8192
|
||||
};
|
||||
|
||||
_videoBitrateModel = new Wt::WStringListModel();
|
||||
for (std::size_t i = 0; i < videoBitrateLimits.size(); ++i)
|
||||
_videoBitrateModel->addString( Wt::WString("{1}").arg( videoBitrateLimits[i] ) );
|
||||
|
||||
}
|
||||
|
||||
SessionData& _sessionData;
|
||||
std::string _userId;
|
||||
Wt::WStringListModel* _audioBitrateModel;
|
||||
Wt::WStringListModel* _videoBitrateModel;
|
||||
};
|
||||
|
||||
const Wt::WFormModel::Field UserFormModel::NameField = "name";
|
||||
const Wt::WFormModel::Field UserFormModel::EmailField = "email";
|
||||
const Wt::WFormModel::Field UserFormModel::PasswordField = "password";
|
||||
const Wt::WFormModel::Field UserFormModel::PasswordConfirmField = "password-confirm";
|
||||
const Wt::WFormModel::Field UserFormModel::AdminField = "admin";
|
||||
const Wt::WFormModel::Field UserFormModel::AudioBitrateLimitField = "audio-bitrate-limit";
|
||||
const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitrate-limit";
|
||||
|
||||
|
||||
UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent)
|
||||
: Wt::WTemplateFormView(parent),
|
||||
_sessionData(sessionData)
|
||||
{
|
||||
|
||||
_model = new UserFormModel(sessionData, userId, this);
|
||||
|
||||
setTemplateText(tr("userForm-template"));
|
||||
addFunction("id", &WTemplate::Functions::id);
|
||||
addFunction("block", &WTemplate::Functions::id);
|
||||
|
||||
// Name
|
||||
setFormWidget(UserFormModel::NameField, new Wt::WLineEdit());
|
||||
|
||||
// Email
|
||||
setFormWidget(UserFormModel::EmailField, new Wt::WLineEdit());
|
||||
|
||||
// Password
|
||||
Wt::WLineEdit* passwordEdit = new Wt::WLineEdit();
|
||||
setFormWidget(UserFormModel::PasswordField, passwordEdit );
|
||||
passwordEdit->setEchoMode(Wt::WLineEdit::Password);
|
||||
|
||||
// Password confirmation
|
||||
Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit();
|
||||
setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit);
|
||||
passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password);
|
||||
|
||||
// Admin Field
|
||||
Wt::WCheckBox *admin = new Wt::WCheckBox();
|
||||
setFormWidget(UserFormModel::AdminField, admin);
|
||||
|
||||
// AudioBitrate
|
||||
Wt::WComboBox *audioBitrateCB = new Wt::WComboBox();
|
||||
setFormWidget(UserFormModel::AudioBitrateLimitField, audioBitrateCB);
|
||||
audioBitrateCB->setStyleClass("span2");
|
||||
audioBitrateCB->setModel(_model->audioBitrateModel());
|
||||
|
||||
// VideoBitrate
|
||||
Wt::WComboBox *videoBitrateCB = new Wt::WComboBox();
|
||||
setFormWidget(UserFormModel::VideoBitrateLimitField, videoBitrateCB);
|
||||
videoBitrateCB->setStyleClass("span2");
|
||||
videoBitrateCB->setModel(_model->videoBitrateModel());
|
||||
|
||||
// Title & Buttons
|
||||
Wt::WString title;
|
||||
if (userId.empty()) {
|
||||
title = Wt::WString("Create user");
|
||||
}
|
||||
else {
|
||||
Database::Handler &db = sessionData.getDatabaseHandler();
|
||||
Wt::Dbo::Transaction transaction (db.getSession());
|
||||
Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId );
|
||||
|
||||
Wt::WString userName;
|
||||
if (authUser.isValid())
|
||||
userName = authUser.identity(Wt::Auth::Identity::LoginName);
|
||||
else
|
||||
; // TODO display user deleted and close the widget
|
||||
|
||||
title = Wt::WString("Edit user {1}").arg(userName);
|
||||
}
|
||||
|
||||
bindString("title", title);
|
||||
|
||||
Wt::WPushButton *saveButton = new Wt::WPushButton();
|
||||
if (userId.empty()) {
|
||||
saveButton->setText("Create user");
|
||||
saveButton->setStyleClass("btn-success");
|
||||
}
|
||||
else
|
||||
{
|
||||
saveButton->setText("Save");
|
||||
saveButton->setStyleClass("btn-primary");
|
||||
}
|
||||
bindWidget("save-button", saveButton);
|
||||
saveButton->clicked().connect(this, &UserFormView::processSave);
|
||||
|
||||
Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel");
|
||||
bindWidget("cancel-button", cancelButton);
|
||||
cancelButton->clicked().connect(this, &UserFormView::processCancel);
|
||||
|
||||
updateView(_model);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
UserFormView::processCancel()
|
||||
{
|
||||
// parent widget will delete this widget
|
||||
completed().emit(false);
|
||||
}
|
||||
|
||||
void
|
||||
UserFormView::processSave()
|
||||
{
|
||||
updateModel(_model);
|
||||
|
||||
|
||||
if (_model->validate())
|
||||
{
|
||||
// commit model into DB
|
||||
if (_model->saveData() )
|
||||
{
|
||||
// parent widget will delete this widget
|
||||
completed().emit(true);
|
||||
}
|
||||
// else TODO display a nice error message
|
||||
}
|
||||
else
|
||||
{
|
||||
updateView(_model);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
} // namespace UserInterface
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef UI_SETTINGS_USER_FORM_VIEW_HPP
|
||||
#define UI_SETTINGS_USER_FORM_VIEW_HPP
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WTemplateFormView>
|
||||
#include <Wt/WSignal>
|
||||
|
||||
#include "common/SessionData.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
namespace Settings {
|
||||
|
||||
class UserFormModel;
|
||||
|
||||
class UserFormView : public Wt::WTemplateFormView
|
||||
{
|
||||
public:
|
||||
UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
// Signal emitted once the form is completed
|
||||
Wt::Signal<bool>& completed() { return _sigCompleted; }
|
||||
|
||||
private:
|
||||
|
||||
Wt::Signal<bool> _sigCompleted;
|
||||
|
||||
SessionData& _sessionData;
|
||||
|
||||
void processSave();
|
||||
void processCancel();
|
||||
|
||||
UserFormModel* _model;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Settings
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <Wt/Auth/Identity>
|
||||
|
||||
#include "SettingsUserFormView.hpp"
|
||||
|
||||
#include "SettingsUsers.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -39,12 +41,7 @@ _sessionData(sessionData)
|
||||
Wt::WPushButton* addBtn = new Wt::WPushButton("Add User");
|
||||
addBtn->setStyleClass("btn-success");
|
||||
container->addWidget( addBtn );
|
||||
addBtn->clicked().connect(this, &Users::handleAddUser);
|
||||
}
|
||||
|
||||
// 2/ the user form
|
||||
{
|
||||
new Wt::WText("This is the user form!", _stack);
|
||||
addBtn->clicked().connect(boost::bind(&Users::handleCreateUser, this, ""));
|
||||
}
|
||||
|
||||
refresh();
|
||||
@@ -55,7 +52,7 @@ Users::refresh(void)
|
||||
{
|
||||
|
||||
assert(_table->rowCount() > 0);
|
||||
for (int i = _table->rowCount() - 1; i > 0; ++i)
|
||||
for (int i = _table->rowCount() - 1; i > 0; --i)
|
||||
_table->deleteRow(i);
|
||||
|
||||
Database::Handler& db = _sessionData.getDatabaseHandler();
|
||||
@@ -66,6 +63,7 @@ Users::refresh(void)
|
||||
|
||||
std::vector<Database::User::pointer> users = Database::User::getAll(db.getSession());
|
||||
|
||||
std::size_t userIndex = 1;
|
||||
for (std::size_t i = 0; i < users.size(); ++i)
|
||||
{
|
||||
|
||||
@@ -75,10 +73,25 @@ Users::refresh(void)
|
||||
userId = oss.str();
|
||||
}
|
||||
|
||||
Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId );
|
||||
Wt::Auth::User authUser;
|
||||
|
||||
_table->elementAt(i + 1, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(i+1)));
|
||||
_table->elementAt(i + 1, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) );
|
||||
// Hack try/catch here since it may fail!
|
||||
try {
|
||||
authUser = db.getUserDatabase().findWithId( userId );
|
||||
}
|
||||
catch(Wt::Dbo::Exception& e)
|
||||
{
|
||||
std::cerr << "Caught exception when getting userId=" << userId << ": " << e.code() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!authUser.isValid()) {
|
||||
std::cerr << "Users::refresh: skipping invalid userId = " << userId << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
_table->elementAt(userIndex, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(userIndex)));
|
||||
_table->elementAt(userIndex, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) );
|
||||
|
||||
Wt::WText* email = new Wt::WText();
|
||||
if (!authUser.email().empty()) {
|
||||
@@ -88,21 +101,23 @@ Users::refresh(void)
|
||||
email->setStyleClass("alert-danger");
|
||||
email->setText(authUser.unverifiedEmail());
|
||||
}
|
||||
_table->elementAt(i + 1, 2)->addWidget( email ) ;
|
||||
_table->elementAt(i + 1, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" ));
|
||||
_table->elementAt(userIndex, 2)->addWidget( email ) ;
|
||||
_table->elementAt(userIndex, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" ));
|
||||
|
||||
Wt::WPushButton* editBtn = new Wt::WPushButton("Edit");
|
||||
_table->elementAt(i + 1, 4)->addWidget(editBtn);
|
||||
editBtn->clicked().connect(boost::bind( &Users::handleEditUser, this, userId));
|
||||
_table->elementAt(userIndex, 4)->addWidget(editBtn);
|
||||
editBtn->clicked().connect(boost::bind( &Users::handleCreateUser, this, userId));
|
||||
|
||||
if (currentUser != authUser)
|
||||
{
|
||||
Wt::WPushButton* delBtn = new Wt::WPushButton("Delete");
|
||||
delBtn->setStyleClass("btn-danger");
|
||||
delBtn->setMargin(5, Wt::Left);
|
||||
_table->elementAt(i + 1, 4)->addWidget(delBtn);
|
||||
_table->elementAt(userIndex, 4)->addWidget(delBtn);
|
||||
delBtn->clicked().connect(boost::bind( &Users::handleDelUser, this, authUser.identity(Wt::Auth::Identity::LoginName), userId));
|
||||
}
|
||||
|
||||
++userIndex;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,36 +137,51 @@ Users::handleDelUser(Wt::WString loginNameIdentity, std::string id)
|
||||
{
|
||||
Database::Handler& db = _sessionData.getDatabaseHandler();
|
||||
|
||||
Wt::Dbo::Transaction transaction(db.getSession());
|
||||
|
||||
// Delete the user
|
||||
Wt::Auth::User authUser = db.getUserDatabase().findWithId( id );
|
||||
|
||||
db.getUserDatabase().deleteUser( authUser );
|
||||
|
||||
// TODO remove Database::User too?
|
||||
Database::User::pointer user = Database::User::getById(db.getSession(), id);
|
||||
if (user)
|
||||
user.remove();
|
||||
|
||||
// TODO Update the view
|
||||
refresh();
|
||||
}
|
||||
|
||||
delete messageBox;
|
||||
}));
|
||||
|
||||
messageBox->show();
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
Users::handleAddUser(void)
|
||||
Users::handleCreateUser(std::string id)
|
||||
{
|
||||
// TODO refresh the User Form for a new user
|
||||
//_stack->setCurrentIndex(1);
|
||||
assert(_stack->count() == 1);
|
||||
|
||||
UserFormView* userFormView = new UserFormView(_sessionData, id, _stack);
|
||||
userFormView->completed().connect(this, &Users::handleUserFormCompleted);
|
||||
|
||||
_stack->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void
|
||||
Users::handleEditUser(std::string id)
|
||||
Users::handleUserFormCompleted(bool changed)
|
||||
{
|
||||
//TODO refresh the User Form to edit the given user
|
||||
//_stack->setCurrentIndex(1);
|
||||
}
|
||||
_stack->setCurrentIndex(0);
|
||||
|
||||
// Refresh the user table if a change has been made
|
||||
if (changed)
|
||||
refresh();
|
||||
|
||||
// Delete the form view
|
||||
delete _stack->widget(1);
|
||||
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
} // namespace Settings
|
||||
|
||||
@@ -19,9 +19,10 @@ class Users : public Wt::WContainerWidget
|
||||
|
||||
private:
|
||||
|
||||
void handleUserFormCompleted(bool changed);
|
||||
|
||||
void handleDelUser(Wt::WString loginNameIdentity, std::string id);
|
||||
void handleEditUser(std::string id);
|
||||
void handleAddUser(void);
|
||||
void handleCreateUser(std::string id); // set the id in order to edit the user
|
||||
|
||||
SessionData& _sessionData;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user