mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-04-07 14:28:52 +00:00
feat(core): Add message of the day
This commit is contained in:
@@ -1586,6 +1586,11 @@ For PostgresSQL, set the database URL to something like:
|
||||
all cache data. You must also set `OCSStoreURL` and `OCSAclURL`
|
||||
if you set this parameter.
|
||||
|
||||
|S |OCSAdminURL
|
||||
|Parameter used to set the database URL so that SOGo can use to store
|
||||
all administration elements.
|
||||
|
||||
|
||||
For PostgresSQL, set the database URL to something like:
|
||||
`postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_cache_folder`.
|
||||
|=======================================================================
|
||||
|
||||
@@ -274,7 +274,7 @@ static BOOL debugLeaks;
|
||||
|
||||
if ([GCSFolderManager singleStoreMode])
|
||||
{
|
||||
urlStrings = [NSArray arrayWithObjects: @"SOGoProfileURL", @"OCSFolderInfoURL", @"OCSStoreURL", @"OCSAclURL", @"OCSCacheFolderURL", nil];
|
||||
urlStrings = [NSArray arrayWithObjects: @"SOGoProfileURL", @"OCSFolderInfoURL", @"OCSStoreURL", @"OCSAclURL", @"OCSCacheFolderURL", @"OCSAdminURL", nil];
|
||||
quickTypeStrings = [NSArray arrayWithObjects: @"contact", @"appointment", nil];
|
||||
combined = YES;
|
||||
}
|
||||
@@ -314,6 +314,9 @@ static BOOL debugLeaks;
|
||||
{
|
||||
fm = [GCSFolderManager defaultFolderManager];
|
||||
|
||||
// Create the sessions table
|
||||
[[fm adminFolder] createFolderIfNotExists];
|
||||
|
||||
// Create the sessions table
|
||||
[[fm sessionsFolder] createFolderIfNotExists];
|
||||
|
||||
|
||||
53
SOPE/GDLContentStore/GCSAdminFolder.h
Normal file
53
SOPE/GDLContentStore/GCSAdminFolder.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* GCSAdminFolder.h - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* Copyright (C) 2023 Alinto
|
||||
*
|
||||
* This file 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 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef GCSALARMSFOLDER_H
|
||||
#define GCSALARMSFOLDER_H
|
||||
|
||||
|
||||
@class NSCalendarDate;
|
||||
@class NSException;
|
||||
@class NSNumber;
|
||||
@class NSString;
|
||||
|
||||
@class GCSFolderManager;
|
||||
|
||||
@interface GCSAdminFolder : NSObject
|
||||
{
|
||||
GCSFolderManager *folderManager;
|
||||
}
|
||||
|
||||
+ (id) alarmsFolderWithFolderManager: (GCSFolderManager *) newFolderManager;
|
||||
|
||||
- (void) setFolderManager: (GCSFolderManager *) newFolderManager;
|
||||
|
||||
/* operations */
|
||||
|
||||
- (NSString *)getMotd;
|
||||
|
||||
- (void) createFolderIfNotExists;
|
||||
- (BOOL) canConnectStore;
|
||||
|
||||
- (NSException *)writeMotd:(NSString *)motd;
|
||||
- (NSException *)deleteMotd;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* GCSALARMSFOLDER_H */
|
||||
347
SOPE/GDLContentStore/GCSAdminFolder.m
Normal file
347
SOPE/GDLContentStore/GCSAdminFolder.m
Normal file
@@ -0,0 +1,347 @@
|
||||
/* GCSAdminFolder.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2023 Alinto
|
||||
*
|
||||
* This file 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 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorContext.h>
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
#import <GDLAccess/EOEntity.h>
|
||||
#import <GDLAccess/EOSQLQualifier.h>
|
||||
|
||||
#import "EOQualifier+GCS.h"
|
||||
#import "GCSChannelManager.h"
|
||||
#import "GCSFolderManager.h"
|
||||
#import "GCSSpecialQueries.h"
|
||||
#import "NSURL+GCS.h"
|
||||
|
||||
#import "GCSAdminFolder.h"
|
||||
|
||||
static NSString *adminFolderURLString = nil;
|
||||
|
||||
#warning GCSAdminFolder should share a common ancestor with GCSFolder
|
||||
|
||||
@implementation GCSAdminFolder
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSUserDefaults *ud;
|
||||
|
||||
if (!adminFolderURLString)
|
||||
{
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
ASSIGN (adminFolderURLString,
|
||||
[ud stringForKey: @"OCSAdminURL"]);
|
||||
}
|
||||
}
|
||||
|
||||
+ (id) alarmsFolderWithFolderManager: (GCSFolderManager *) newFolderManager
|
||||
{
|
||||
GCSAlarmsFolder *newFolder;
|
||||
|
||||
if (adminFolderURLString)
|
||||
{
|
||||
newFolder = [self new];
|
||||
[newFolder autorelease];
|
||||
[newFolder setFolderManager: newFolderManager];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"'OCSAdminURL' is not set"];
|
||||
newFolder = nil;
|
||||
}
|
||||
|
||||
return newFolder;
|
||||
}
|
||||
|
||||
- (void) setFolderManager: (GCSFolderManager *) newFolderManager
|
||||
{
|
||||
ASSIGN (folderManager, newFolderManager);
|
||||
}
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (NSURL *) _location
|
||||
{
|
||||
NSURL *location;
|
||||
|
||||
if (adminFolderURLString)
|
||||
location = [NSURL URLWithString: adminFolderURLString];
|
||||
else
|
||||
{
|
||||
[self warnWithFormat: @"'OCSAdminURL' is not set"];
|
||||
location = nil;
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
- (GCSChannelManager *) _channelManager
|
||||
{
|
||||
return [folderManager channelManager];
|
||||
}
|
||||
|
||||
- (NSString *) _storeTableName
|
||||
{
|
||||
return [[self _location] gcsTableName];
|
||||
}
|
||||
|
||||
- (EOEntity *) _storeTableEntityForChannel: (EOAdaptorChannel *) tc
|
||||
{
|
||||
static EOEntity *entity = nil;
|
||||
EOAttribute *attribute;
|
||||
NSString *tableName;
|
||||
NSString *columns[] = { @"c_key", @"c_content", nil };
|
||||
NSString **column;
|
||||
NSMutableArray *keys;
|
||||
NSDictionary *types;
|
||||
|
||||
if (!entity)
|
||||
{
|
||||
entity = [EOEntity new];
|
||||
tableName = [self _storeTableName];
|
||||
[entity setName: tableName];
|
||||
[entity setExternalName: tableName];
|
||||
|
||||
types = [[tc specialQueries] adminAttributeTypes];
|
||||
|
||||
column = columns;
|
||||
while (*column)
|
||||
{
|
||||
attribute = [EOAttribute new];
|
||||
[attribute setName: *column];
|
||||
[attribute setColumnName: *column];
|
||||
[attribute setExternalType: [types objectForKey: *column]];
|
||||
[entity addAttribute: attribute];
|
||||
[attribute release];
|
||||
column++;
|
||||
}
|
||||
|
||||
keys = [NSMutableArray arrayWithCapacity: 1];
|
||||
[keys addObject: [entity attributeNamed: @"c_key"]];
|
||||
[entity setPrimaryKeyAttributes: keys];
|
||||
|
||||
keys = [NSMutableArray arrayWithCapacity: 1];
|
||||
[keys addObject: [entity attributeNamed: @"c_content"]];
|
||||
[entity setClassProperties: keys];
|
||||
|
||||
[entity setAttributesUsedForLocking: [NSArray array]];
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/* connection */
|
||||
|
||||
- (EOAdaptorChannel *) _acquireStoreChannel
|
||||
{
|
||||
return [[self _channelManager] acquireOpenChannelForURL: [self _location]];
|
||||
}
|
||||
|
||||
- (void) _releaseChannel: (EOAdaptorChannel *) _channel
|
||||
{
|
||||
[[self _channelManager] releaseChannel:_channel immediately: YES];
|
||||
}
|
||||
|
||||
- (BOOL) canConnectStore
|
||||
{
|
||||
return [[self _channelManager] canConnect:[self _location]];
|
||||
}
|
||||
|
||||
- (void) createFolderIfNotExists
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
NSString *sql, *tableName;
|
||||
GCSSpecialQueries *queries;
|
||||
|
||||
tc = [self _acquireStoreChannel];
|
||||
tableName = [self _storeTableName];
|
||||
|
||||
queries = [tc specialQueries];
|
||||
|
||||
sql = [NSString stringWithFormat: @"SELECT 1 FROM %@ WHERE 1 = 2",
|
||||
[self _storeTableName]];
|
||||
if ([tc evaluateExpressionX: sql])
|
||||
{
|
||||
sql = [queries createAdminFolderWithName: tableName];
|
||||
if (![tc evaluateExpressionX: sql])
|
||||
[self logWithFormat:
|
||||
@"admin folder table '%@' successfully created!",
|
||||
tableName];
|
||||
}
|
||||
else
|
||||
[tc cancelFetch];
|
||||
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
/* operations */
|
||||
|
||||
/* table has the following fields:
|
||||
c_key VARCHAR(255) NOT NULL
|
||||
c_content MEDIUMTEXT NOT NULL
|
||||
*/
|
||||
|
||||
- (NSDictionary *) recordForEntryWithKey: (NSString *) key
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
NSException *error;
|
||||
NSArray *attrs;
|
||||
NSDictionary *record;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
|
||||
record = nil;
|
||||
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat:
|
||||
@"c_key='%@'",
|
||||
key];
|
||||
[qualifier autorelease];
|
||||
|
||||
[context beginTransaction];
|
||||
error = [tc selectAttributesX: [entity attributesUsedForFetch]
|
||||
describedByQualifier: qualifier
|
||||
fetchOrder: nil
|
||||
lock: NO];
|
||||
if (error)
|
||||
[self errorWithFormat:@"%s: cannot execute fetch: %@",
|
||||
__PRETTY_FUNCTION__, error];
|
||||
else
|
||||
{
|
||||
attrs = [tc describeResults: NO];
|
||||
record = [tc fetchAttributes: attrs withZone: NULL];
|
||||
[tc cancelFetch];
|
||||
}
|
||||
[context rollbackTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
- (NSString *) getMotd {
|
||||
NSDictionary *r;
|
||||
|
||||
r = [self recordForEntryWithKey: @"motd"];
|
||||
if (r && [r objectForKey:@"c_content"]) {
|
||||
return [r objectForKey:@"c_content"];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *) _newRecordWithKey: (NSString *) key
|
||||
content: (NSString *) content
|
||||
{
|
||||
return [NSDictionary dictionaryWithObjectsAndKeys: key, @"c_key",
|
||||
content, @"c_content",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (NSException *) writeMotd: (NSString *)motd
|
||||
{
|
||||
NSDictionary *record, *newRecord;
|
||||
NSException *error;
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
|
||||
error = nil;
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
newRecord = [self _newRecordWithKey: @"motd" content: motd];
|
||||
record = [self recordForEntryWithKey: @"motd"];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
[context beginTransaction];
|
||||
if (record)
|
||||
{
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat:
|
||||
@"c_key='motd'"];
|
||||
[qualifier autorelease];
|
||||
error = [tc updateRowX: newRecord describedByQualifier: qualifier];
|
||||
}
|
||||
else
|
||||
error = [tc insertRowX: newRecord forEntity: entity];
|
||||
if (error)
|
||||
{
|
||||
[context rollbackTransaction];
|
||||
[self errorWithFormat:@"%s: cannot write record: %@",
|
||||
__PRETTY_FUNCTION__, error];
|
||||
}
|
||||
else
|
||||
[context commitTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSException *) deleteRecordForKey: (NSString *) key
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
NSException *error;
|
||||
|
||||
error = nil;
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat:
|
||||
@"c_key='%@'",
|
||||
key];
|
||||
[qualifier autorelease];
|
||||
[context beginTransaction];
|
||||
error = [tc deleteRowsDescribedByQualifierX: qualifier];
|
||||
if (error)
|
||||
{
|
||||
[context rollbackTransaction];
|
||||
[self errorWithFormat:@"%s: cannot delete record: %@",
|
||||
__PRETTY_FUNCTION__, error];
|
||||
}
|
||||
else
|
||||
[context commitTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSException *) deleteMotd
|
||||
{
|
||||
return [self deleteRecordForKey:@"motd"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -202,7 +202,7 @@ static NSString *alarmsFolderURLString = nil;
|
||||
|
||||
/* table has the following fields:
|
||||
c_path VARCHAR(255) NOT NULL
|
||||
c_name VARCHAR(255) NOT NULLo
|
||||
c_name VARCHAR(255) NOT NULL
|
||||
c_uid VARCHAR(255) NOT NULL
|
||||
c_recurrence_id INT NULL
|
||||
c_alarm_number INT NOT NULL
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*/
|
||||
|
||||
@class NSString, NSArray, NSURL, NSDictionary, NSException;
|
||||
@class GCSChannelManager, GCSAlarmsFolder, GCSFolder, GCSFolderType, GCSSessionsFolder;
|
||||
@class GCSChannelManager, GCSAlarmsFolder, GCSAdminFolder, GCSFolder, GCSFolderType, GCSSessionsFolder;
|
||||
|
||||
@interface GCSFolderManager : NSObject
|
||||
{
|
||||
@@ -89,6 +89,9 @@
|
||||
/* sessions */
|
||||
- (GCSSessionsFolder *)sessionsFolder;
|
||||
|
||||
/* admin */
|
||||
- (GCSAdminFolder *)adminFolder;
|
||||
|
||||
/* folder types */
|
||||
|
||||
- (GCSFolderType *)folderTypeWithName:(NSString *)_name;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#import "GCSChannelManager.h"
|
||||
#import "EOAdaptorChannel+GCS.h"
|
||||
#import "GCSAlarmsFolder.h"
|
||||
#import "GCSAdminFolder.h"
|
||||
#import "GCSFolder.h"
|
||||
#import "GCSFolderType.h"
|
||||
#import "GCSSessionsFolder.h"
|
||||
@@ -497,6 +498,12 @@ static BOOL _singleStoreMode = NO;
|
||||
return [GCSSessionsFolder sessionsFolderWithFolderManager: self];
|
||||
}
|
||||
|
||||
/* admin */
|
||||
- (GCSAdminFolder *) adminFolder
|
||||
{
|
||||
return [GCSAdminFolder alarmsFolderWithFolderManager: self];
|
||||
}
|
||||
|
||||
- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
|
||||
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
|
||||
{
|
||||
|
||||
@@ -39,8 +39,11 @@
|
||||
- (NSString *) createSessionsFolderWithName: (NSString *) tableName;
|
||||
- (NSDictionary *) sessionsAttributeTypes;
|
||||
|
||||
- (NSString *) updateCPathInFolderInfo: (NSString *) tableName
|
||||
withCPath2: (NSString *) c_path2;
|
||||
- (NSString *)createAdminFolderWithName:(NSString *)tableName;
|
||||
- (NSDictionary *)adminAttributeTypes;
|
||||
|
||||
- (NSString *) updateCPathInFolderInfo:(NSString *)tableName
|
||||
withCPath2:(NSString *)c_path2;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -81,6 +81,20 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) createAdminFolderWithName: (NSString *) tableName
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *) adminAttributeTypes
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
@@ -157,6 +171,30 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createAdminFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_key VARCHAR(255) NOT NULL,"
|
||||
@" c_content TEXT NOT NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) adminAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar" forKey: @"c_key"];
|
||||
[types setObject: @"varchar" forKey: @"c_content"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
@@ -262,6 +300,30 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createAdminFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_key VARCHAR(255) NOT NULL,"
|
||||
@" c_content MEDIUMTEXT NOT NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) adminAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar" forKey: @"c_key"];
|
||||
[types setObject: @"varchar" forKey: @"c_content"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
@@ -367,6 +429,30 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createAdminFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_key VARCHAR2(255) NOT NULL,"
|
||||
@" c_content VARCHAR2(65535) NOT NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) adminAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar2" forKey: @"c_key"];
|
||||
[types setObject: @"varchar2" forKey: @"c_content"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
|
||||
@@ -25,6 +25,7 @@ libGDLContentStore_HEADER_FILES += \
|
||||
EOAdaptorChannel+GCS.h \
|
||||
\
|
||||
GCSAlarmsFolder.h \
|
||||
GCSAdminFolder.h \
|
||||
GCSContext.h \
|
||||
GCSFieldInfo.h \
|
||||
GCSFolder.h \
|
||||
@@ -41,6 +42,7 @@ libGDLContentStore_OBJC_FILES += \
|
||||
EOQualifier+GCS.m \
|
||||
\
|
||||
GCSAlarmsFolder.m \
|
||||
GCSAdminFolder.m \
|
||||
GCSContext.m \
|
||||
GCSFieldInfo.m \
|
||||
GCSFolder.m \
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
-- OCSSessionsFolderURL -> sogo_sessions_folder
|
||||
-- OCSStoreURL -> sogo_store
|
||||
-- SOGoProfileURL -> sogo_user_profile
|
||||
-- OCSAdminURL -> sogo_admin
|
||||
--
|
||||
-- SOGo needs to know MySQL has full Unicode coverage;
|
||||
-- the following needs to be put in sogo.conf:
|
||||
@@ -158,6 +159,12 @@ CREATE TABLE sogo_store (
|
||||
PRIMARY KEY (c_folder_id,c_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE sogo_admin (
|
||||
c_key varchar(255) NOT NULL DEFAULT '',
|
||||
c_content mediumtext NOT NULL,
|
||||
PRIMARY KEY (c_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE sogo_user_profile (
|
||||
c_uid varchar(255) NOT NULL,
|
||||
c_defaults longtext,
|
||||
|
||||
@@ -96,7 +96,9 @@ SOGo_HEADER_FILES = \
|
||||
NGMimeBodyPart+SOGo.h \
|
||||
NGMimeFileData+SOGo.h \
|
||||
\
|
||||
SOGoMobileProvision.h
|
||||
SOGoMobileProvision.h \
|
||||
\
|
||||
SOGoAdmin.h
|
||||
|
||||
all::
|
||||
@touch SOGoBuild.m
|
||||
@@ -186,7 +188,9 @@ SOGo_OBJC_FILES = \
|
||||
NGMimeBodyPart+SOGo.m \
|
||||
NGMimeFileData+SOGo.m \
|
||||
\
|
||||
SOGoMobileProvision.m
|
||||
SOGoMobileProvision.m \
|
||||
\
|
||||
SOGoAdmin.m
|
||||
|
||||
SOGo_C_FILES += lmhash.c aes.c crypt_blowfish.c pkcs5_pbkdf2.c
|
||||
|
||||
|
||||
45
SoObjects/SOGo/SOGoAdmin.h
Normal file
45
SoObjects/SOGo/SOGoAdmin.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright (C) 2023 Alinto
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
SOGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
SOGo 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with SOGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SOGoAdmin_H__
|
||||
#define __SOGoAdmin_H__
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class NSObject;
|
||||
@class NSException;
|
||||
@class NSString;
|
||||
|
||||
@interface SOGoAdmin : NSObject
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
+ (id)sharedInstance;
|
||||
|
||||
- (BOOL)isConfigured;
|
||||
- (NSString *)getMotd;
|
||||
- (NSException *)deleteMotd;
|
||||
- (NSException *)saveMotd:(NSString *)motd;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __SOGoAdmin_H__ */
|
||||
105
SoObjects/SOGo/SOGoAdmin.m
Normal file
105
SoObjects/SOGo/SOGoAdmin.m
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (C) 2023 Alinto
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
SOGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
SOGo 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with SOGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import "SOGoAdmin.h"
|
||||
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
|
||||
#import "NSString+Utilities.h"
|
||||
#import "SOGoCache.h"
|
||||
|
||||
static const NSString *kCacheMotdKey = @"admin-motd";
|
||||
|
||||
@implementation SOGoAdmin
|
||||
|
||||
+ (id)sharedInstance
|
||||
{
|
||||
static id admin = nil;
|
||||
|
||||
if (!admin)
|
||||
admin = [self new];
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
if (self = [super init]) {
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
}
|
||||
|
||||
- (BOOL) isConfigured
|
||||
{
|
||||
return nil != [[GCSFolderManager defaultFolderManager] adminFolder];
|
||||
}
|
||||
|
||||
- (NSString *)getMotd
|
||||
{
|
||||
NSString *cache;
|
||||
NSString *value;
|
||||
|
||||
cache = [[SOGoCache sharedCache] valueForKey:kCacheMotdKey];
|
||||
if (!cache) {
|
||||
value = [[[GCSFolderManager defaultFolderManager] adminFolder] getMotd];
|
||||
if (value) {
|
||||
[[SOGoCache sharedCache] setValue:[[[GCSFolderManager defaultFolderManager] adminFolder] getMotd] forKey:kCacheMotdKey];
|
||||
cache = value;
|
||||
} else {
|
||||
cache = @" "; // Empty string won't set cache
|
||||
[[SOGoCache sharedCache] setValue:cache forKey:kCacheMotdKey];
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
- (NSException *)deleteMotd
|
||||
{
|
||||
NSException *error;
|
||||
|
||||
error = [[[GCSFolderManager defaultFolderManager] adminFolder] deleteMotd];
|
||||
if (!error) {
|
||||
[[SOGoCache sharedCache] removeValueForKey:kCacheMotdKey];
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSException *)saveMotd:(NSString *)motd
|
||||
{
|
||||
NSException *error;
|
||||
NSString *safeMotd;
|
||||
|
||||
safeMotd = [motd stringWithoutHTMLInjection: NO];
|
||||
error = [[[GCSFolderManager defaultFolderManager] adminFolder] writeMotd: safeMotd];
|
||||
if (!error) {
|
||||
[[SOGoCache sharedCache] setValue:safeMotd forKey:kCacheMotdKey];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@end /* SOGoAdmin */
|
||||
@@ -25,3 +25,7 @@
|
||||
"No resource" = "No resource";
|
||||
"Any Authenticated User" = "Any Authenticated User";
|
||||
"Public Access" = "Public Access";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
|
||||
@@ -25,3 +25,7 @@
|
||||
"No resource" = "Aucune ressource";
|
||||
"Any Authenticated User" = "Tout utilisateur authentifié";
|
||||
"Public Access" = "Accès public";
|
||||
"Save" = "Sauvegarder";
|
||||
"Clear" = "Effacer";
|
||||
"Message of the day" = "Message du jour";
|
||||
"Message of the day has been saved" = "Le message du jour a été sauvegardé";
|
||||
@@ -13,7 +13,8 @@ AdministrationUI_OBJC_FILES = \
|
||||
\
|
||||
UIxAdministration.m \
|
||||
UIxAdministrationAclEditor.m \
|
||||
UIxAdministrationFilterPanel.m
|
||||
UIxAdministrationFilterPanel.m \
|
||||
UIxAdministrationMotd.m
|
||||
|
||||
AdministrationUI_RESOURCE_FILES += \
|
||||
product.plist
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#import <SoObjects/SOGo/SOGoUser.h>
|
||||
//#import "../../Main/SOGo.h"
|
||||
|
||||
#import <SOGo/SOGoAdmin.h>
|
||||
|
||||
#import "UIxAdministration.h"
|
||||
|
||||
@implementation UIxAdministration
|
||||
@@ -53,12 +55,18 @@
|
||||
return @"Administration";
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
|
||||
inContext: (WOContext*) context
|
||||
{
|
||||
return [[request method] isEqualToString: @"POST"];
|
||||
}
|
||||
|
||||
- (BOOL) isAdminTableConfigured
|
||||
{
|
||||
return [[SOGoAdmin sharedInstance] isConfigured];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* Theme Preview */
|
||||
|
||||
33
UI/AdministrationUI/UIxAdministrationMotd.h
Normal file
33
UI/AdministrationUI/UIxAdministrationMotd.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/* UIxAdministrationMotd.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2023 Alinto
|
||||
*
|
||||
* This file 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 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef UIXMOTD_H
|
||||
#define UIXMOTD_H
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@interface UIxAdministrationMotd : UIxComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif /* UIXMOTD_H */
|
||||
110
UI/AdministrationUI/UIxAdministrationMotd.m
Normal file
110
UI/AdministrationUI/UIxAdministrationMotd.m
Normal file
@@ -0,0 +1,110 @@
|
||||
/* UIxAdministrationMotd.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2023 Alinto
|
||||
*
|
||||
* This file 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 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import "UIxAdministrationMotd.h"
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoAdmin.h>
|
||||
|
||||
@implementation UIxAdministrationMotd
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (WOResponse *) getAction
|
||||
{
|
||||
WOResponse *response;
|
||||
SOGoAdmin *admin;
|
||||
NSDictionary *jsonResponse;
|
||||
|
||||
admin = [SOGoAdmin sharedInstance];
|
||||
if ([admin isConfigured]) {
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: nil != [admin getMotd] ? [admin getMotd] : @""
|
||||
forKey: @"motd"];
|
||||
response = [self responseWithStatus: 200
|
||||
andJSONRepresentation: jsonResponse];
|
||||
} else {
|
||||
response = [self responseWithStatus: 500
|
||||
andString: @"Missing folder configuration"];
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
- (WOResponse *) saveAction
|
||||
{
|
||||
WORequest *request;
|
||||
WOResponse *response;
|
||||
SOGoUser *user;
|
||||
NSException *error;
|
||||
NSDictionary *data;
|
||||
SOGoAdmin *admin;
|
||||
|
||||
error = nil;
|
||||
admin = [SOGoAdmin sharedInstance];
|
||||
user = [context activeUser];
|
||||
|
||||
if ([user isSuperUser]) {
|
||||
if ([admin isConfigured]) {
|
||||
data = [[[context request] contentAsString] objectFromJSONString];
|
||||
if ([data objectForKey: @"motd"]
|
||||
&& [[data objectForKey: @"motd"] isKindOfClass: [NSString class]]
|
||||
&& [[data objectForKey: @"motd"] length] > 0) {
|
||||
error = [admin saveMotd: [data objectForKey: @"motd"]];
|
||||
} else {
|
||||
error = [admin deleteMotd];
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
response = [self responseWithStatus: 200
|
||||
andString: @"OK"];
|
||||
} else {
|
||||
response = [self responseWithStatus: 500
|
||||
andString: @"Error while storing information"];
|
||||
}
|
||||
|
||||
} else {
|
||||
response = [self responseWithStatus: 500
|
||||
andString: @"Missing folder configuration"];
|
||||
}
|
||||
} else {
|
||||
response = [self responseWithStatus: 503
|
||||
andString: @"Forbidden"];
|
||||
}
|
||||
|
||||
response = [self responseWithStatus: 200
|
||||
andString: @"OK"];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -22,6 +22,20 @@
|
||||
protectedBy = "View";
|
||||
pageName = "UIxThemePreview";
|
||||
};
|
||||
UIxAdministrationMotd = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxAdministrationMotd";
|
||||
};
|
||||
getMotd = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxAdministrationMotd";
|
||||
actionName = "get";
|
||||
};
|
||||
saveMotd = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxAdministrationMotd";
|
||||
actionName = "save";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#import <SOGo/SOGoWebAuthenticator.h>
|
||||
#import <SOGo/SOGoEmptyAuthenticator.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoAdmin.h>
|
||||
|
||||
#if defined(MFA_CONFIG)
|
||||
#include <liboath/oath.h>
|
||||
@@ -1043,4 +1044,20 @@ static const NSString *kJwtKey = @"jwt";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)motd
|
||||
{
|
||||
return [[SOGoAdmin sharedInstance] getMotd];
|
||||
}
|
||||
|
||||
- (NSString *)motdEscaped
|
||||
{
|
||||
return [[[SOGoAdmin sharedInstance] getMotd] stringWithoutHTMLInjection: YES];
|
||||
}
|
||||
|
||||
- (BOOL)hasMotd
|
||||
{
|
||||
return [[SOGoAdmin sharedInstance] getMotd] && [[[SOGoAdmin sharedInstance] getMotd] length] > 1;
|
||||
}
|
||||
|
||||
@end /* SOGoRootPage */
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
"Last" = "Last used";
|
||||
"Default Module " = "Default Module";
|
||||
"SOGo Version" = "SOGo Version";
|
||||
"Administration" = "Administration";
|
||||
|
||||
/* Confirmation asked when changing the language */
|
||||
"Save preferences and reload page now?" = "Save preferences and reload page now?";
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
"Last" = "Dernier utilisé";
|
||||
"Default Module " = "Module par défaut";
|
||||
"SOGo Version" = "Version";
|
||||
"Administration" = "Administration";
|
||||
|
||||
/* Confirmation asked when changing the language */
|
||||
"Save preferences and reload page now?" = "Sauvegarder les préférences et recharger maintenant?";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
title="moduleName"
|
||||
const:jsFiles="Common.js, Administration.js,
|
||||
Administration.services.js, Preferences.services.js,
|
||||
Contacts.services.js, Scheduler.services.js">
|
||||
Contacts.services.js, Scheduler.services.js, vendor/ckeditor/ckeditor.js, Common/sgCkeditor.component.js">
|
||||
|
||||
<main class="view"
|
||||
layout="row" layout-fill="layout-fill"
|
||||
@@ -38,6 +38,14 @@
|
||||
<md-icon>palette</md-icon>
|
||||
<p class="sg-item-name"><var:string label:value="Theme Preview"/></p>
|
||||
</md-list-item>
|
||||
<var:if condition="isAdminTableConfigured">
|
||||
<md-list-item ng-click="app.go('motd')"
|
||||
ui-sref="administration.motd"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>insert_comment</md-icon>
|
||||
<p class="sg-item-name"><var:string label:value="Message of the day"/></p>
|
||||
</md-list-item>
|
||||
</var:if>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</md-sidenav>
|
||||
|
||||
27
UI/Templates/AdministrationUI/UIxAdministrationMotd.wox
Normal file
27
UI/Templates/AdministrationUI/UIxAdministrationMotd.wox
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE container>
|
||||
<container
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label"
|
||||
>
|
||||
<md-toolbar layout="row" layout-align="space-between center" class="sg-toolbar-main">
|
||||
<var:component className="UIxTopnavToolbar" />
|
||||
</md-toolbar>
|
||||
<md-content class="md-padding ng-scope">
|
||||
<div class="pseudo-input-container ng-scope">
|
||||
<label class="pseudo-input-label"><var:string label:value="Message of the day"/></label>
|
||||
<sg-ckeditor
|
||||
class="ng-cloak"
|
||||
config="ctrl.ckConfig"
|
||||
ck-margin="8px"
|
||||
ng-model="ctrl.motd"><!-- HTML editor --></sg-ckeditor>
|
||||
<div layout="row" layout-align="end center" class="layout-align-end-center layout-row">
|
||||
<button class="md-primary md-button ng-binding md-ink-ripple" ng-click="ctrl.clear()"><var:string label:value="Clear"/></button>
|
||||
<button class="md-primary md-button ng-binding md-ink-ripple" ng-click="ctrl.save()"><var:string label:value="Save"/></button>
|
||||
</div>
|
||||
</div>
|
||||
</md-content>
|
||||
|
||||
</container>
|
||||
@@ -24,337 +24,349 @@
|
||||
layout-gt-md="row" layout-align-gt-md="center start" layout-fill="layout-fill"
|
||||
ui-view="login"
|
||||
ng-controller="LoginController as app">
|
||||
<md-content id="loginContent" class="ng-cloak md-whiteframe-3dp" flex="100"
|
||||
layout="column" layout-gt-md="row" layout-align="start stretch"
|
||||
<md-content class="ng-cloak" flex="100"
|
||||
layout="column" layout-align="start stretch"
|
||||
ng-show="app.showLogin">
|
||||
<div class="sg-logo" flex-gt-md="50">
|
||||
<div layout="row" class="md-padding">
|
||||
<div class="md-flex hide show-gt-md"><!-- push logo to the right on larger screens --></div>
|
||||
<img const:alt="*" class="md-margin" rsrc:src="img/sogo-full.svg"/>
|
||||
<var:if condition="hasMotd">
|
||||
<div flex="100" class="motd hide show-gt-md">
|
||||
<var:string var:value="motd" const:escapeHTML="NO" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="sg-login md-default-theme md-bg md-accent" flex-gt-md="50">
|
||||
<div id="login" class="sg-login-content md-padding">
|
||||
<form name="loginForm" layout="column"
|
||||
ng-cloak="ng-cloak"
|
||||
ng-submit="app.login()">
|
||||
<var:if condition="hasLoginSuffix">
|
||||
<input type="hidden" ng-model="app.creds.loginSuffix" var:value="loginSuffix"/>
|
||||
</var:if>
|
||||
<div id="loginContent" layout="column" layout-gt-md="row" flex="100" class="md-whiteframe-3dp">
|
||||
<div class="sg-logo" flex-gt-md="50">
|
||||
<div layout="row" class="md-padding">
|
||||
<div class="md-flex hide show-gt-md"><!-- push logo to the right on larger screens --></div>
|
||||
<img const:alt="*" class="md-margin" rsrc:src="img/sogo-full.svg"/>
|
||||
</div>
|
||||
<var:if condition="hasMotd">
|
||||
<div layout="row">
|
||||
<div class="motd hide-gt-md"><var:string var:value="motdEscaped" const:escapeHTML="NO" /></div>
|
||||
</div>
|
||||
</var:if>
|
||||
<div ng-if="!app.loginState">
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Username"/></label>
|
||||
<md-icon>person</md-icon>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Password"/></label>
|
||||
<md-icon>vpn_key</md-icon>
|
||||
<input id="passwordField" type="password" ng-model="app.creds.password" ng-required="true"/>
|
||||
<md-icon id="password-visibility-icon" ng-click="app.changePasswordVisibility()">visibility</md-icon>
|
||||
</md-input-container>
|
||||
|
||||
</div>
|
||||
<div class="sg-login md-default-theme md-bg md-accent" flex-gt-md="50">
|
||||
<div id="login" class="sg-login-content md-padding">
|
||||
<form name="loginForm" layout="column"
|
||||
ng-cloak="ng-cloak"
|
||||
ng-submit="app.login()">
|
||||
<var:if condition="hasLoginSuffix">
|
||||
<input type="hidden" ng-model="app.creds.loginSuffix" var:value="loginSuffix"/>
|
||||
</var:if>
|
||||
<div ng-if="!app.loginState">
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Username"/></label>
|
||||
<md-icon>person</md-icon>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Password"/></label>
|
||||
<md-icon>vpn_key</md-icon>
|
||||
<input id="passwordField" type="password" ng-model="app.creds.password" ng-required="true"/>
|
||||
<md-icon id="password-visibility-icon" ng-click="app.changePasswordVisibility()">visibility</md-icon>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<!-- LANGUAGES SELECT -->
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>language</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<label><var:string label:value="choose"/></label>
|
||||
<md-select ng-model="app.creds.language"
|
||||
var:placeholder="localizedLanguage"
|
||||
ng-change="app.changeLanguage($event)">
|
||||
<var:foreach list="languages" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="languageText"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<!-- LANGUAGES SELECT -->
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>language</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<label><var:string label:value="choose"/></label>
|
||||
<md-select ng-model="app.creds.language"
|
||||
var:placeholder="localizedLanguage"
|
||||
ng-change="app.changeLanguage($event)">
|
||||
<var:foreach list="languages" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="languageText"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<!-- DOMAINS SELECT -->
|
||||
<var:if condition="hasLoginDomains">
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>domain</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<md-select class="md-flex" ng-model="app.creds.domain" label:placeholder="choose" ng-change="app.retrievePasswordRecoveryEnabled()">
|
||||
<var:foreach list="loginDomains" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="item"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<md-switch class="md-accent md-hue-2"
|
||||
ng-model="app.creds.rememberLogin"
|
||||
label:arial-label="Remember username">
|
||||
<var:string label:value="Remember username"/>
|
||||
</md-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password recovery -->
|
||||
<div layout="row" layout-align="center center" ng-if="app.passwordRecovery.passwordRecoveryEnabled">
|
||||
<div ng-if="app.showLogin">
|
||||
<a href="#" ng-click="app.passwordRecoveryInfo()" sg-ripple-click="loginContent" class="password-lost-link"><var:string label:value="Password lost"/></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONNECT BUTTON -->
|
||||
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="About"
|
||||
ng-click="app.showAbout()">
|
||||
<md-icon>info</md-icon>
|
||||
</md-button>
|
||||
<div>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="!app.loginState"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sg-ripple class="md-default-theme md-accent md-bg"
|
||||
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
||||
<sg-ripple-content class="md-flex ng-hide"
|
||||
layout="column" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch="app.loginState">
|
||||
|
||||
<!-- Authenticating -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="authenticating">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Authenticating"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<var:if condition="isTotpEnabled">
|
||||
<!-- TOTP Code -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpcode">
|
||||
<div flex="80" flex-sm="50" flex-gt-sm="40">
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Verification Code"/></label>
|
||||
<md-icon>lock</md-icon>
|
||||
<input type="text"
|
||||
ng-pattern="app.verificationCodePattern"
|
||||
ng-model="app.creds.verificationCode"
|
||||
ng-required="app.loginState == 'totpcode'"
|
||||
sg-focus-on="totpcode"/>
|
||||
<div class="sg-hint"><var:string label:value="Enter the 6-digit verification code from your TOTP application."/></div>
|
||||
</md-input-container>
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="Cancel"
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_backward</md-icon>
|
||||
</md-button>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="app.loginState == 'totpcode'"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
ng-click="app.login()">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TOTP has been disabled -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpdisabled">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">
|
||||
<var:string label:value="Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application."/>
|
||||
<!-- DOMAINS SELECT -->
|
||||
<var:if condition="hasLoginDomains">
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>domain</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<md-select class="md-flex" ng-model="app.creds.domain" label:placeholder="choose" ng-change="app.retrievePasswordRecoveryEnabled()">
|
||||
<var:foreach list="loginDomains" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="item"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<md-switch class="md-accent md-hue-2"
|
||||
ng-model="app.creds.rememberLogin"
|
||||
label:arial-label="Remember username">
|
||||
<var:string label:value="Remember username"/>
|
||||
</md-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
|
||||
<!-- Password recovery -->
|
||||
<div layout="row" layout-align="center center" ng-if="app.passwordRecovery.passwordRecoveryEnabled">
|
||||
<div ng-if="app.showLogin">
|
||||
<a href="#" ng-click="app.passwordRecoveryInfo()" sg-ripple-click="loginContent" class="password-lost-link"><var:string label:value="Password lost"/></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONNECT BUTTON -->
|
||||
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="About"
|
||||
ng-click="app.showAbout()">
|
||||
<md-icon>info</md-icon>
|
||||
</md-button>
|
||||
<div>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="!app.loginState"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sg-ripple class="md-default-theme md-accent md-bg"
|
||||
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
||||
<sg-ripple-content class="md-flex ng-hide"
|
||||
layout="column" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch="app.loginState">
|
||||
|
||||
<!-- Authenticating -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="authenticating">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Authenticating"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<var:if condition="isTotpEnabled">
|
||||
<!-- TOTP Code -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpcode">
|
||||
<div flex="80" flex-sm="50" flex-gt-sm="40">
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Verification Code"/></label>
|
||||
<md-icon>lock</md-icon>
|
||||
<input type="text"
|
||||
ng-pattern="app.verificationCodePattern"
|
||||
ng-model="app.creds.verificationCode"
|
||||
ng-required="app.loginState == 'totpcode'"
|
||||
sg-focus-on="totpcode"/>
|
||||
<div class="sg-hint"><var:string label:value="Enter the 6-digit verification code from your TOTP application."/></div>
|
||||
</md-input-container>
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="Cancel"
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_backward</md-icon>
|
||||
</md-button>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="app.loginState == 'totpcode'"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
ng-click="app.login()">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TOTP has been disabled -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpdisabled">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">
|
||||
<var:string label:value="Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application."/>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Password policy: Password is expired / password recovery-->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordchange">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="!app.isInPasswordRecoveryMode()">watch_later</md-icon>
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="app.isInPasswordRecoveryMode()">vpn_key</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Your password has expired, please enter a new one below"/>
|
||||
</div>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Please enter a new password below"/>
|
||||
</div>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column">
|
||||
<md-input-container class="md-block" flex="flex" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<label><var:string label:value="Current password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.oldPassword"/>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex="flex">
|
||||
<label><var:string label:value="New password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex="flex">
|
||||
<label><var:string label:value="Confirmation"/>
|
||||
</label>
|
||||
<input type="password" name="newPasswordConfirmation" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/>
|
||||
<div ng-messages="loginForm.newPasswordConfirmation.$error">
|
||||
<div ng-message="newPasswordMismatch"><var:string label:value="Passwords don't match"/></div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.changePassword()" type="button" ng-disabled="!app.canChangePassword(loginForm)">
|
||||
<var:string label:value="Change"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password policy: Grace period -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="passwordwillexpire">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">{{app.errorMessage}}</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password recovery -->
|
||||
<var:if condition="hasPasswordRecovery">
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordrecovery">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">vpn_key</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding" layout-align="center center">
|
||||
<div ng-if="app.passwordRecovery.showLoader">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- password recovery progress --></md-progress-circular>
|
||||
</div>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoveryQuestion }}
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Answer"/></label>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.passwordRecovery.passwordRecoveryQuestionAnswer" />
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoverySecondaryEmailText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center" ng-if="!app.passwordRecovery.showLoader">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryCheck()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryEmail()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="sendrecoverymail">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">local_shipping</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding">
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<var:string label:value="A password reset link has been sent, please check your recovery e-mail mailbox and click on the link"/>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Logged in -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="logged">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="message">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Password policy: Password is expired / password recovery-->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordchange">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="!app.isInPasswordRecoveryMode()">watch_later</md-icon>
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="app.isInPasswordRecoveryMode()">vpn_key</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Your password has expired, please enter a new one below"/>
|
||||
</div>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Please enter a new password below"/>
|
||||
</div>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column">
|
||||
<md-input-container class="md-block" flex="flex" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<label><var:string label:value="Current password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.oldPassword"/>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex="flex">
|
||||
<label><var:string label:value="New password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex="flex">
|
||||
<label><var:string label:value="Confirmation"/>
|
||||
</label>
|
||||
<input type="password" name="newPasswordConfirmation" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/>
|
||||
<div ng-messages="loginForm.newPasswordConfirmation.$error">
|
||||
<div ng-message="newPasswordMismatch"><var:string label:value="Passwords don't match"/></div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.changePassword()" type="button" ng-disabled="!app.canChangePassword(loginForm)">
|
||||
<var:string label:value="Change"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password policy: Grace period -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="passwordwillexpire">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">{{app.errorMessage}}</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<!-- Error -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="error">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</sg-ripple-content>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Password recovery -->
|
||||
<var:if condition="hasPasswordRecovery">
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordrecovery">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">vpn_key</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding" layout-align="center center">
|
||||
<div ng-if="app.passwordRecovery.showLoader">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- password recovery progress --></md-progress-circular>
|
||||
</div>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoveryQuestion }}
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Answer"/></label>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.passwordRecovery.passwordRecoveryQuestionAnswer" />
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoverySecondaryEmailText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center" ng-if="!app.passwordRecovery.showLoader">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryCheck()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryEmail()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="sendrecoverymail">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">local_shipping</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding">
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<var:string label:value="A password reset link has been sent, please check your recovery e-mail mailbox and click on the link"/>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Logged in -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="logged">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="message">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="error">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
||||
</div>
|
||||
|
||||
</sg-ripple-content>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</md-content>
|
||||
|
||||
</main>
|
||||
|
||||
@@ -1493,4 +1493,4 @@
|
||||
</script>
|
||||
|
||||
|
||||
</var:component>
|
||||
</var:component>
|
||||
@@ -4,7 +4,7 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.AdministrationUI', ['ui.router', 'SOGo.Common', 'SOGo.Authentication', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.SchedulerUI'])
|
||||
angular.module('SOGo.AdministrationUI', ['ui.router', 'SOGo.Common', 'SOGo.Authentication', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'sgCkeditor'])
|
||||
.config(configure)
|
||||
.run(runBlock);
|
||||
|
||||
@@ -56,6 +56,16 @@
|
||||
controllerAs: 'ctrl'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('administration.motd', {
|
||||
url: '/motd',
|
||||
views: {
|
||||
module: {
|
||||
templateUrl: 'UIxAdministrationMotd', // UI/Templates/Administration/UIxAdministrationMotd.wox
|
||||
controller: 'AdministrationMotdController',
|
||||
controllerAs: 'ctrl'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if none of the above states are matched, use this as the fallback
|
||||
|
||||
@@ -29,6 +29,34 @@
|
||||
return new Administration(); // return unique instance
|
||||
}];
|
||||
|
||||
/**
|
||||
* @function $getMotd
|
||||
* @memberof Administration.prototype
|
||||
* @desc Get the motd to the server.
|
||||
*/
|
||||
Administration.prototype.$getMotd = function () {
|
||||
var _this = this;
|
||||
|
||||
return Administration.$$resource.fetch("Administration/getMotd")
|
||||
.then(function (data) {
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $saveMotd
|
||||
* @memberof Administration.prototype
|
||||
* @desc Save the motd to the server.
|
||||
*/
|
||||
Administration.prototype.$saveMotd = function (message) {
|
||||
var _this = this;
|
||||
|
||||
return Administration.$$resource.save("Administration", { motd: message }, { action: "saveMotd" })
|
||||
.then(function (data) {
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
/* Initialize module if necessary */
|
||||
try {
|
||||
angular.module('SOGo.AdministrationUI');
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* JavaScript for SOGoAdministration */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
AdministrationMotdController.$inject = ['$timeout', '$state', '$mdMedia', '$mdToast', 'sgConstant', 'Administration', 'sgSettings'];
|
||||
function AdministrationMotdController($timeout, $state, $mdMedia, $mdToast, sgConstant, Administration, Settings) {
|
||||
var vm = this;
|
||||
vm.administration = Administration;
|
||||
vm.motd = null;
|
||||
vm.save = save;
|
||||
vm.clear = clear;
|
||||
vm.ckConfig = {
|
||||
'autoGrow_minHeight': 200,
|
||||
removeButtons: 'Save,NewPage,Preview,Print,Templates,Cut,Copy,Paste,PasteText,PasteFromWord,Undo,Redo,Find,Replace,SelectAll,Scayt,Form,Checkbox,Radio,TextField,Textarea,Select,Button,Image,HiddenField,CopyFormatting,RemoveFormat,NumberedList,BulletedList,Outdent,Indent,Blockquote,CreateDiv,BidiLtr,BidiRtl,Language,Unlink,Anchor,Flash,Table,HorizontalRule,Smiley,SpecialChar,PageBreak,Iframe,Styles,Format,Maximize,ShowBlocks,About,Strike,Subscript,Superscript,Underline,Emojipanel,Emoji,'
|
||||
};
|
||||
|
||||
this.administration.$getMotd().then(function (data) {
|
||||
if (data && data.motd) {
|
||||
vm.motd = data.motd;
|
||||
}
|
||||
});
|
||||
|
||||
function save() {
|
||||
this.administration.$saveMotd(vm.motd).then(function () {
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.textContent(l('Message of the day has been saved'))
|
||||
.position(sgConstant.toastPosition)
|
||||
.hideDelay(3000));
|
||||
});
|
||||
}
|
||||
|
||||
function clear() {
|
||||
console.log('HEY');
|
||||
vm.motd = '';
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('SOGo.AdministrationUI')
|
||||
.controller('AdministrationMotdController', AdministrationMotdController);
|
||||
|
||||
})();
|
||||
@@ -1,4 +1,5 @@
|
||||
/// LoginUI.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
||||
@use "sass:color";
|
||||
|
||||
$sg-login-width: grid-step(5);
|
||||
|
||||
@@ -7,6 +8,12 @@ $sg-login-width: grid-step(5);
|
||||
// Keep content centered
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
|
||||
#loginContent {
|
||||
background-color: $colorGrey50;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.sg-logo {
|
||||
// Center image
|
||||
@@ -17,6 +24,31 @@ $sg-login-width: grid-step(5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.motd {
|
||||
margin-top: 50px;
|
||||
margin-bottom: 50px;
|
||||
justify-content: center;
|
||||
margin-left: 2%;
|
||||
margin-right: 2%;
|
||||
padding: 50px;
|
||||
animation: fade-in ease 1.5s;
|
||||
border: solid 5px sg-color($sogoGreen, 500);
|
||||
}
|
||||
|
||||
.md-whiteframe-3dp {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.password-lost-link {
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 0.9em;
|
||||
@@ -52,6 +84,9 @@ $sg-login-width: grid-step(5);
|
||||
max-width: 75%;
|
||||
}
|
||||
}
|
||||
#loginContent {
|
||||
margin-top: 0;
|
||||
}
|
||||
.sg-login {
|
||||
// Let the form take all available space
|
||||
flex-grow: 1;
|
||||
@@ -61,6 +96,11 @@ $sg-login-width: grid-step(5);
|
||||
margin: auto;
|
||||
max-width: $sg-login-width;
|
||||
}
|
||||
.motd {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user