feat(core): Add message of the day

This commit is contained in:
smizrahi
2024-01-22 14:34:22 +01:00
parent 0d0eda2698
commit eee50697b0
32 changed files with 1362 additions and 326 deletions
@@ -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é";
+2 -1
View File
@@ -13,7 +13,8 @@ AdministrationUI_OBJC_FILES = \
\
UIxAdministration.m \
UIxAdministrationAclEditor.m \
UIxAdministrationFilterPanel.m
UIxAdministrationFilterPanel.m \
UIxAdministrationMotd.m
AdministrationUI_RESOURCE_FILES += \
product.plist
+8
View File
@@ -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 */
@@ -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
View 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
+14
View File
@@ -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";
};
};
};
};
+17
View File
@@ -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>
@@ -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>
+327 -315
View File
@@ -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;
}
}
/**