Merge pull request #997 from mattermost/PLT-586
PLT-586 fixing issues with security alert
Этот коммит содержится в:
@@ -104,6 +104,8 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.SetDefaults()
|
||||||
|
|
||||||
if err := cfg.IsValid(); err != nil {
|
if err := cfg.IsValid(); err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"EnableIncomingWebhooks": true,
|
"EnableIncomingWebhooks": true,
|
||||||
"EnablePostUsernameOverride": false,
|
"EnablePostUsernameOverride": false,
|
||||||
"EnablePostIconOverride": false,
|
"EnablePostIconOverride": false,
|
||||||
"EnableTesting": false
|
"EnableTesting": false,
|
||||||
|
"EnableSecurityFixAlert": true
|
||||||
},
|
},
|
||||||
"TeamSettings": {
|
"TeamSettings": {
|
||||||
"SiteName": "Mattermost",
|
"SiteName": "Mattermost",
|
||||||
@@ -77,8 +78,7 @@
|
|||||||
},
|
},
|
||||||
"PrivacySettings": {
|
"PrivacySettings": {
|
||||||
"ShowEmailAddress": true,
|
"ShowEmailAddress": true,
|
||||||
"ShowFullName": true,
|
"ShowFullName": true
|
||||||
"EnableSecurityFixAlert": true
|
|
||||||
},
|
},
|
||||||
"GitLabSettings": {
|
"GitLabSettings": {
|
||||||
"Enable": false,
|
"Enable": false,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"EnableIncomingWebhooks": true,
|
"EnableIncomingWebhooks": true,
|
||||||
"EnablePostUsernameOverride": false,
|
"EnablePostUsernameOverride": false,
|
||||||
"EnablePostIconOverride": false,
|
"EnablePostIconOverride": false,
|
||||||
"EnableTesting": false
|
"EnableTesting": false,
|
||||||
|
"EnableSecurityFixAlert": true
|
||||||
},
|
},
|
||||||
"TeamSettings": {
|
"TeamSettings": {
|
||||||
"SiteName": "Mattermost",
|
"SiteName": "Mattermost",
|
||||||
@@ -77,8 +78,7 @@
|
|||||||
},
|
},
|
||||||
"PrivacySettings": {
|
"PrivacySettings": {
|
||||||
"ShowEmailAddress": true,
|
"ShowEmailAddress": true,
|
||||||
"ShowFullName": true,
|
"ShowFullName": true
|
||||||
"EnableSecurityFixAlert": true
|
|
||||||
},
|
},
|
||||||
"GitLabSettings": {
|
"GitLabSettings": {
|
||||||
"Enable": false,
|
"Enable": false,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"EnableIncomingWebhooks": true,
|
"EnableIncomingWebhooks": true,
|
||||||
"EnablePostUsernameOverride": false,
|
"EnablePostUsernameOverride": false,
|
||||||
"EnablePostIconOverride": false,
|
"EnablePostIconOverride": false,
|
||||||
"EnableTesting": false
|
"EnableTesting": false,
|
||||||
|
"EnableSecurityFixAlert": true
|
||||||
},
|
},
|
||||||
"TeamSettings": {
|
"TeamSettings": {
|
||||||
"SiteName": "Mattermost",
|
"SiteName": "Mattermost",
|
||||||
@@ -77,8 +78,7 @@
|
|||||||
},
|
},
|
||||||
"PrivacySettings": {
|
"PrivacySettings": {
|
||||||
"ShowEmailAddress": true,
|
"ShowEmailAddress": true,
|
||||||
"ShowFullName": true,
|
"ShowFullName": true
|
||||||
"EnableSecurityFixAlert": true
|
|
||||||
},
|
},
|
||||||
"GitLabSettings": {
|
"GitLabSettings": {
|
||||||
"Enable": false,
|
"Enable": false,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"EnableIncomingWebhooks": true,
|
"EnableIncomingWebhooks": true,
|
||||||
"EnablePostUsernameOverride": false,
|
"EnablePostUsernameOverride": false,
|
||||||
"EnablePostIconOverride": false,
|
"EnablePostIconOverride": false,
|
||||||
"EnableTesting": false
|
"EnableTesting": false,
|
||||||
|
"EnableSecurityFixAlert": true
|
||||||
},
|
},
|
||||||
"TeamSettings": {
|
"TeamSettings": {
|
||||||
"SiteName": "Mattermost",
|
"SiteName": "Mattermost",
|
||||||
@@ -77,8 +78,7 @@
|
|||||||
},
|
},
|
||||||
"PrivacySettings": {
|
"PrivacySettings": {
|
||||||
"ShowEmailAddress": true,
|
"ShowEmailAddress": true,
|
||||||
"ShowFullName": true,
|
"ShowFullName": true
|
||||||
"EnableSecurityFixAlert": true
|
|
||||||
},
|
},
|
||||||
"GitLabSettings": {
|
"GitLabSettings": {
|
||||||
"Enable": false,
|
"Enable": false,
|
||||||
|
|||||||
@@ -81,28 +81,28 @@ func main() {
|
|||||||
func securityAndDiagnosticsJob() {
|
func securityAndDiagnosticsJob() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if utils.Cfg.PrivacySettings.EnableSecurityFixAlert && model.IsOfficalBuild() {
|
if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
|
||||||
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
||||||
props := result.Data.(model.StringMap)
|
props := result.Data.(model.StringMap)
|
||||||
lastSecurityTime, _ := strconv.ParseInt(props["LastSecurityTime"], 10, 0)
|
lastSecurityTime, _ := strconv.ParseInt(props["LastSecurityTime"], 10, 0)
|
||||||
currentTime := model.GetMillis()
|
currentTime := model.GetMillis()
|
||||||
|
|
||||||
id := props["DiagnosticId"]
|
|
||||||
if len(id) == 0 {
|
|
||||||
id = model.NewId()
|
|
||||||
systemId := &model.System{Name: "DiagnosticId", Value: id}
|
|
||||||
<-api.Srv.Store.System().Save(systemId)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set(utils.PROP_DIAGNOSTIC_ID, id)
|
|
||||||
v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
|
|
||||||
v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
|
|
||||||
v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
|
|
||||||
v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
|
|
||||||
|
|
||||||
if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
|
if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
|
||||||
l4g.Info("Checking for security update from Mattermost")
|
l4g.Debug("Checking for security update from Mattermost")
|
||||||
|
|
||||||
|
id := props["DiagnosticId"]
|
||||||
|
if len(id) == 0 {
|
||||||
|
id = model.NewId()
|
||||||
|
systemId := &model.System{Name: "DiagnosticId", Value: id}
|
||||||
|
<-api.Srv.Store.System().Save(systemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_ID, id)
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
|
||||||
|
|
||||||
systemSecurityLastTime := &model.System{Name: "LastSecurityTime", Value: strconv.FormatInt(currentTime, 10)}
|
systemSecurityLastTime := &model.System{Name: "LastSecurityTime", Value: strconv.FormatInt(currentTime, 10)}
|
||||||
if lastSecurityTime == 0 {
|
if lastSecurityTime == 0 {
|
||||||
@@ -111,6 +111,14 @@ func securityAndDiagnosticsJob() {
|
|||||||
<-api.Srv.Store.System().Update(systemSecurityLastTime)
|
<-api.Srv.Store.System().Update(systemSecurityLastTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ucr := <-api.Srv.Store.User().GetTotalActiveUsersCount(); ucr.Err == nil {
|
||||||
|
v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
|
||||||
|
}
|
||||||
|
|
||||||
res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
|
res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l4g.Error("Failed to get security update information from Mattermost.")
|
l4g.Error("Failed to get security update information from Mattermost.")
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ type ServiceSettings struct {
|
|||||||
EnablePostUsernameOverride bool
|
EnablePostUsernameOverride bool
|
||||||
EnablePostIconOverride bool
|
EnablePostIconOverride bool
|
||||||
EnableTesting bool
|
EnableTesting bool
|
||||||
|
EnableSecurityFixAlert *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSOSettings struct {
|
type SSOSettings struct {
|
||||||
@@ -110,9 +111,8 @@ type RateLimitSettings struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PrivacySettings struct {
|
type PrivacySettings struct {
|
||||||
ShowEmailAddress bool
|
ShowEmailAddress bool
|
||||||
ShowFullName bool
|
ShowFullName bool
|
||||||
EnableSecurityFixAlert bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamSettings struct {
|
type TeamSettings struct {
|
||||||
@@ -163,6 +163,13 @@ func ConfigFromJson(data io.Reader) *Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Config) SetDefaults() {
|
||||||
|
if o.ServiceSettings.EnableSecurityFixAlert == nil {
|
||||||
|
o.ServiceSettings.EnableSecurityFixAlert = new(bool)
|
||||||
|
*o.ServiceSettings.EnableSecurityFixAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Config) IsValid() *AppError {
|
func (o *Config) IsValid() *AppError {
|
||||||
|
|
||||||
if o.ServiceSettings.MaximumLoginAttempts <= 0 {
|
if o.ServiceSettings.MaximumLoginAttempts <= 0 {
|
||||||
|
|||||||
@@ -530,3 +530,24 @@ func (us SqlUserStore) GetTotalUsersCount() StoreChannel {
|
|||||||
|
|
||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
time := model.GetMillis() - (1000 * 60 * 60 * 12)
|
||||||
|
|
||||||
|
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlUserStore.GetTotalActiveUsersCount", "We could not count the users", err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = count
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func TestUserStoreGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserCountt(t *testing.T) {
|
func TestUserCount(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
u1 := model.User{}
|
u1 := model.User{}
|
||||||
@@ -224,6 +224,24 @@ func TestUserCountt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActiveUserCount(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
u1 := model.User{}
|
||||||
|
u1.TeamId = model.NewId()
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(&u1))
|
||||||
|
|
||||||
|
if result := <-store.User().GetTotalActiveUsersCount(); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
count := result.Data.(int64)
|
||||||
|
if count <= 0 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUserStoreGetProfiles(t *testing.T) {
|
func TestUserStoreGetProfiles(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ type UserStore interface {
|
|||||||
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
|
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
|
||||||
GetForExport(teamId string) StoreChannel
|
GetForExport(teamId string) StoreChannel
|
||||||
GetTotalUsersCount() StoreChannel
|
GetTotalUsersCount() StoreChannel
|
||||||
|
GetTotalActiveUsersCount() StoreChannel
|
||||||
GetSystemAdminProfiles() StoreChannel
|
GetSystemAdminProfiles() StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ func LoadConfig(fileName string) {
|
|||||||
CfgFileName = fileName
|
CfgFileName = fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
if err := config.IsValid(); err != nil {
|
if err := config.IsValid(); err != nil {
|
||||||
panic("Error validating config file=" + fileName + ", err=" + err.Message)
|
panic("Error validating config file=" + fileName + ", err=" + err.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,23 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DIAGNOSTIC_URL = "https://d7zmvsa9e04kk.cloudfront.net"
|
DIAGNOSTIC_URL = "https://d7zmvsa9e04kk.cloudfront.net"
|
||||||
|
|
||||||
PROP_DIAGNOSTIC_ID = "id"
|
PROP_DIAGNOSTIC_ID = "id"
|
||||||
PROP_DIAGNOSTIC_CATEGORY = "c"
|
PROP_DIAGNOSTIC_CATEGORY = "c"
|
||||||
VAL_DIAGNOSTIC_CATEGORY_DEFAULT = "d"
|
VAL_DIAGNOSTIC_CATEGORY_DEFAULT = "d"
|
||||||
PROP_DIAGNOSTIC_BUILD = "b"
|
PROP_DIAGNOSTIC_BUILD = "b"
|
||||||
PROP_DIAGNOSTIC_DATABASE = "db"
|
PROP_DIAGNOSTIC_DATABASE = "db"
|
||||||
PROP_DIAGNOSTIC_OS = "os"
|
PROP_DIAGNOSTIC_OS = "os"
|
||||||
PROP_DIAGNOSTIC_USER_COUNT = "uc"
|
PROP_DIAGNOSTIC_USER_COUNT = "uc"
|
||||||
|
PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SendDiagnostic(values url.Values) {
|
func SendDiagnostic(values url.Values) {
|
||||||
if Cfg.PrivacySettings.EnableSecurityFixAlert && model.IsOfficalBuild() {
|
if *Cfg.ServiceSettings.EnableSecurityFixAlert {
|
||||||
|
|
||||||
res, err := http.Get(DIAGNOSTIC_URL + "/i?" + values.Encode())
|
res, err := http.Get(DIAGNOSTIC_URL + "/i?" + values.Encode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export default class PrivacySettings extends React.Component {
|
|||||||
var config = this.props.config;
|
var config = this.props.config;
|
||||||
config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked;
|
config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked;
|
||||||
config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).checked;
|
config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).checked;
|
||||||
config.PrivacySettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
|
|
||||||
|
|
||||||
Client.saveConfig(
|
Client.saveConfig(
|
||||||
config,
|
config,
|
||||||
@@ -137,39 +136,6 @@ export default class PrivacySettings extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='EnableSecurityFixAlert'
|
|
||||||
>
|
|
||||||
{'Send Error and Diagnostic: '}
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnableSecurityFixAlert'
|
|
||||||
value='true'
|
|
||||||
ref='EnableSecurityFixAlert'
|
|
||||||
defaultChecked={this.props.config.PrivacySettings.EnableSecurityFixAlert}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
{'true'}
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnableSecurityFixAlert'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.PrivacySettings.EnableSecurityFixAlert}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
{'false'}
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>{'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<div className='col-sm-12'>
|
<div className='col-sm-12'>
|
||||||
{serverError}
|
{serverError}
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ export default class ServiceSettings extends React.Component {
|
|||||||
|
|
||||||
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
|
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
|
||||||
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
|
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
|
||||||
//config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
|
|
||||||
config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
|
config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
|
||||||
config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
|
config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
|
||||||
config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked;
|
config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked;
|
||||||
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked;
|
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked;
|
||||||
|
config.ServiceSettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
|
||||||
|
|
||||||
|
//config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
|
||||||
|
|
||||||
var MaximumLoginAttempts = 10;
|
var MaximumLoginAttempts = 10;
|
||||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
|
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
|
||||||
@@ -304,6 +306,39 @@ export default class ServiceSettings extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='form-group'>
|
||||||
|
<label
|
||||||
|
className='control-label col-sm-4'
|
||||||
|
htmlFor='EnableSecurityFixAlert'
|
||||||
|
>
|
||||||
|
{'Enable Security Alerts: '}
|
||||||
|
</label>
|
||||||
|
<div className='col-sm-8'>
|
||||||
|
<label className='radio-inline'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='EnableSecurityFixAlert'
|
||||||
|
value='true'
|
||||||
|
ref='EnableSecurityFixAlert'
|
||||||
|
defaultChecked={this.props.config.ServiceSettings.EnableSecurityFixAlert}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
{'true'}
|
||||||
|
</label>
|
||||||
|
<label className='radio-inline'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='EnableSecurityFixAlert'
|
||||||
|
value='false'
|
||||||
|
defaultChecked={!this.props.config.ServiceSettings.EnableSecurityFixAlert}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
{'false'}
|
||||||
|
</label>
|
||||||
|
<p className='help-text'>{'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<div className='col-sm-12'>
|
<div className='col-sm-12'>
|
||||||
{serverError}
|
{serverError}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user