mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-26 21:27:40 -05:00
Add custom branding functionality (#2667)
This commit is contained in:
committed by
Christopher Speller
parent
5b96ad59c5
commit
49ab8b2161
@@ -35,6 +35,8 @@ func InitAdmin(r *mux.Router) {
|
||||
sr.Handle("/save_compliance_report", ApiUserRequired(saveComplianceReport)).Methods("POST")
|
||||
sr.Handle("/compliance_reports", ApiUserRequired(getComplianceReports)).Methods("GET")
|
||||
sr.Handle("/download_compliance_report/{id:[A-Za-z0-9]+}", ApiUserRequired(downloadComplianceReport)).Methods("GET")
|
||||
sr.Handle("/upload_brand_image", ApiAdminSystemRequired(uploadBrandImage)).Methods("POST")
|
||||
sr.Handle("/get_brand_image", ApiAppHandlerTrustRequester(getBrandImage)).Methods("GET")
|
||||
}
|
||||
|
||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -422,3 +424,77 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if len(utils.Cfg.FileSettings.DriverName) == 0 {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.storage.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
if r.ContentLength > model.MAX_FILE_SIZE {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.too_large.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusRequestEntityTooLarge
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(model.MAX_FILE_SIZE); err != nil {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.parse.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
|
||||
m := r.MultipartForm
|
||||
|
||||
imageArray, ok := m.File["image"]
|
||||
if !ok {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.no_file.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
|
||||
if len(imageArray) <= 0 {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.array.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
|
||||
brandInterface := einterfaces.GetBrandInterface()
|
||||
if brandInterface == nil {
|
||||
c.Err = model.NewLocAppError("uploadBrandImage", "api.admin.upload_brand_image.not_available.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
if err := brandInterface.SaveBrandImage(imageArray[0]); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("")
|
||||
|
||||
rdata := map[string]string{}
|
||||
rdata["status"] = "OK"
|
||||
w.Write([]byte(model.MapToJson(rdata)))
|
||||
}
|
||||
|
||||
func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if len(utils.Cfg.FileSettings.DriverName) == 0 {
|
||||
c.Err = model.NewLocAppError("getBrandImage", "api.admin.get_brand_image.storage.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
brandInterface := einterfaces.GetBrandInterface()
|
||||
if brandInterface == nil {
|
||||
c.Err = model.NewLocAppError("getBrandImage", "api.admin.get_brand_image.not_available.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
if img, err := brandInterface.GetBrandImage(); err != nil {
|
||||
w.Write(nil)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Write(img)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -149,7 +149,7 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + filename
|
||||
|
||||
if err := writeFile(buf.Bytes(), path); err != nil {
|
||||
if err := WriteFile(buf.Bytes(), path); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func handleImagesAndForget(filenames []string, fileData [][]byte, teamId, channe
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeFile(buf.Bytes(), dest+name+"_thumb.jpg"); err != nil {
|
||||
if err := WriteFile(buf.Bytes(), dest+name+"_thumb.jpg"); err != nil {
|
||||
l4g.Error(utils.T("api.file.handle_images_forget.upload_thumb.error"), channelId, userId, filename, err)
|
||||
return
|
||||
}
|
||||
@@ -260,7 +260,7 @@ func handleImagesAndForget(filenames []string, fileData [][]byte, teamId, channe
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeFile(buf.Bytes(), dest+name+"_preview.jpg"); err != nil {
|
||||
if err := WriteFile(buf.Bytes(), dest+name+"_preview.jpg"); err != nil {
|
||||
l4g.Error(utils.T("api.file.handle_images_forget.upload_preview.error"), channelId, userId, filename, err)
|
||||
return
|
||||
}
|
||||
@@ -440,7 +440,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func getFileAndForget(path string, fileData chan []byte) {
|
||||
go func() {
|
||||
data, getErr := readFile(path)
|
||||
data, getErr := ReadFile(path)
|
||||
if getErr != nil {
|
||||
l4g.Error(getErr)
|
||||
fileData <- nil
|
||||
@@ -506,7 +506,7 @@ func getExport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err.StatusCode = http.StatusForbidden
|
||||
return
|
||||
}
|
||||
data, err := readFile(EXPORT_PATH + EXPORT_FILENAME)
|
||||
data, err := ReadFile(EXPORT_PATH + EXPORT_FILENAME)
|
||||
if err != nil {
|
||||
c.Err = model.NewLocAppError("getExport", "api.file.get_export.retrieve.app_error", nil, err.Error())
|
||||
return
|
||||
@@ -517,7 +517,7 @@ func getExport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func writeFile(f []byte, path string) *model.AppError {
|
||||
func WriteFile(f []byte, path string) *model.AppError {
|
||||
|
||||
if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
|
||||
var auth aws.Auth
|
||||
@@ -540,14 +540,14 @@ func writeFile(f []byte, path string) *model.AppError {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.NewLocAppError("writeFile", "api.file.write_file.s3.app_error", nil, err.Error())
|
||||
return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error())
|
||||
}
|
||||
} else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
|
||||
if err := writeFileLocally(f, utils.Cfg.FileSettings.Directory+path); err != nil {
|
||||
if err := WriteFileLocally(f, utils.Cfg.FileSettings.Directory+path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return model.NewLocAppError("writeFile", "api.file.write_file.configured.app_error", nil, "")
|
||||
return model.NewLocAppError("WriteFile", "api.file.write_file.configured.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -574,7 +574,7 @@ func moveFile(oldPath, newPath string) *model.AppError {
|
||||
return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if err := writeFile(fileBytes, newPath); err != nil {
|
||||
if err := WriteFile(fileBytes, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
|
||||
@@ -588,19 +588,19 @@ func moveFile(oldPath, newPath string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeFileLocally(f []byte, path string) *model.AppError {
|
||||
func WriteFileLocally(f []byte, path string) *model.AppError {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil {
|
||||
return model.NewLocAppError("writeFile", "api.file.write_file_locally.create_dir.app_error", nil, err.Error())
|
||||
return model.NewLocAppError("WriteFile", "api.file.write_file_locally.create_dir.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, f, 0644); err != nil {
|
||||
return model.NewLocAppError("writeFile", "api.file.write_file_locally.writing.app_error", nil, err.Error())
|
||||
return model.NewLocAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFile(path string) ([]byte, *model.AppError) {
|
||||
func ReadFile(path string) ([]byte, *model.AppError) {
|
||||
|
||||
if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
|
||||
var auth aws.Auth
|
||||
@@ -620,18 +620,18 @@ func readFile(path string) ([]byte, *model.AppError) {
|
||||
if f != nil {
|
||||
return f, nil
|
||||
} else if tries >= 3 {
|
||||
return nil, model.NewLocAppError("readFile", "api.file.read_file.get.app_error", nil, "path="+path+", err="+err.Error())
|
||||
return nil, model.NewLocAppError("ReadFile", "api.file.read_file.get.app_error", nil, "path="+path+", err="+err.Error())
|
||||
}
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
}
|
||||
} else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
|
||||
if f, err := ioutil.ReadFile(utils.Cfg.FileSettings.Directory + path); err != nil {
|
||||
return nil, model.NewLocAppError("readFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
|
||||
return nil, model.NewLocAppError("ReadFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
|
||||
} else {
|
||||
return f, nil
|
||||
}
|
||||
} else {
|
||||
return nil, model.NewLocAppError("readFile", "api.file.read_file.configured.app_error", nil, "")
|
||||
return nil, model.NewLocAppError("ReadFile", "api.file.read_file.configured.app_error", nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-5
@@ -54,7 +54,7 @@ func InitUser(r *mux.Router) {
|
||||
sr.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
|
||||
sr.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
|
||||
sr.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
|
||||
sr.Handle("/generate_mfa_qr", ApiUserRequired(generateMfaQrCode)).Methods("GET")
|
||||
sr.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
|
||||
sr.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST")
|
||||
|
||||
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
||||
@@ -1150,14 +1150,14 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png"
|
||||
|
||||
if data, err := readFile(path); err != nil {
|
||||
if data, err := ReadFile(path); err != nil {
|
||||
|
||||
if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeFile(img, path); err != nil {
|
||||
if err := WriteFile(img, path); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
@@ -1185,7 +1185,13 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(10000000); err != nil {
|
||||
if r.ContentLength > model.MAX_FILE_SIZE {
|
||||
c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.too_large.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusRequestEntityTooLarge
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(model.MAX_FILE_SIZE); err != nil {
|
||||
c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.parse.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
@@ -1245,7 +1251,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png"
|
||||
|
||||
if err := writeFile(buf.Bytes(), path); err != nil {
|
||||
if err := WriteFile(buf.Bytes(), path); err != nil {
|
||||
c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
|
||||
+3
-1
@@ -32,7 +32,9 @@
|
||||
"EnableUserCreation": true,
|
||||
"RestrictCreationToDomains": "",
|
||||
"RestrictTeamNames": true,
|
||||
"EnableTeamListing": false
|
||||
"EnableTeamListing": false,
|
||||
"EnableCustomBrand": false,
|
||||
"CustomBrandText": ""
|
||||
},
|
||||
"SqlSettings": {
|
||||
"DriverName": "mysql",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package einterfaces
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
type BrandInterface interface {
|
||||
SaveBrandImage(*multipart.FileHeader) *model.AppError
|
||||
GetBrandImage() ([]byte, *model.AppError)
|
||||
}
|
||||
|
||||
var theBrandInterface BrandInterface
|
||||
|
||||
func RegisterBrandInterface(newInterface BrandInterface) {
|
||||
theBrandInterface = newInterface
|
||||
}
|
||||
|
||||
func GetBrandInterface() BrandInterface {
|
||||
return theBrandInterface
|
||||
}
|
||||
@@ -47,6 +47,66 @@
|
||||
"id": "September",
|
||||
"translation": "September"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.storage.app_error",
|
||||
"translation": "Unable to upload image. Image storage is not configured."
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.too_large.app_error",
|
||||
"translation": "Unable to upload file. File is too large."
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.parse.app_error",
|
||||
"translation": "Could not parse multipart form"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.no_file.app_error",
|
||||
"translation": "No file under 'image' in request"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.array.app_error",
|
||||
"translation": "Empty array under 'image' in request"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.upload_brand_image.not_available.app_error",
|
||||
"translation": "Custom branding is not configured or supported on this server"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.get_brand_image.storage.app_error",
|
||||
"translation": "Image storage is not configured."
|
||||
},
|
||||
{
|
||||
"id": "api.admin.get_brand_image.not_available.app_error",
|
||||
"translation": "Custom branding is not configured or supported on this server"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_system.get_by_name.app_error",
|
||||
"translation": "We couldn't find the system variable."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.open.app_error",
|
||||
"translation": "Unable to open the image."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.decode_config.app_error",
|
||||
"translation": "Unable to decode image config."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.too_large.app_error",
|
||||
"translation": "Unable to open image. Image is too large."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.decode.app_error",
|
||||
"translation": "Unable to decode image."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.encode.app_error",
|
||||
"translation": "Unable to encode image as PNG."
|
||||
},
|
||||
{
|
||||
"id": "ent.brand.save_brand_image.save_image.app_error",
|
||||
"translation": "Unable to save image"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.file_read_error",
|
||||
"translation": "Error reading log file"
|
||||
|
||||
@@ -158,6 +158,8 @@ type TeamSettings struct {
|
||||
RestrictCreationToDomains string
|
||||
RestrictTeamNames *bool
|
||||
EnableTeamListing *bool
|
||||
EnableCustomBrand *bool
|
||||
CustomBrandText *string
|
||||
}
|
||||
|
||||
type LdapSettings struct {
|
||||
@@ -296,6 +298,16 @@ func (o *Config) SetDefaults() {
|
||||
*o.TeamSettings.EnableTeamListing = false
|
||||
}
|
||||
|
||||
if o.TeamSettings.EnableCustomBrand == nil {
|
||||
o.TeamSettings.EnableCustomBrand = new(bool)
|
||||
*o.TeamSettings.EnableCustomBrand = false
|
||||
}
|
||||
|
||||
if o.TeamSettings.CustomBrandText == nil {
|
||||
o.TeamSettings.CustomBrandText = new(string)
|
||||
*o.TeamSettings.CustomBrandText = ""
|
||||
}
|
||||
|
||||
if o.EmailSettings.EnableSignInWithEmail == nil {
|
||||
o.EmailSettings.EnableSignInWithEmail = new(bool)
|
||||
|
||||
|
||||
+11
-5
@@ -32,11 +32,12 @@ type Customer struct {
|
||||
}
|
||||
|
||||
type Features struct {
|
||||
Users *int `json:"users"`
|
||||
LDAP *bool `json:"ldap"`
|
||||
MFA *bool `json:"mfa"`
|
||||
GoogleSSO *bool `json:"google_sso"`
|
||||
Compliance *bool `json:"compliance"`
|
||||
Users *int `json:"users"`
|
||||
LDAP *bool `json:"ldap"`
|
||||
MFA *bool `json:"mfa"`
|
||||
GoogleSSO *bool `json:"google_sso"`
|
||||
Compliance *bool `json:"compliance"`
|
||||
CustomBrand *bool `json:"custom_brand"`
|
||||
}
|
||||
|
||||
func (f *Features) SetDefaults() {
|
||||
@@ -64,6 +65,11 @@ func (f *Features) SetDefaults() {
|
||||
f.Compliance = new(bool)
|
||||
*f.Compliance = true
|
||||
}
|
||||
|
||||
if f.CustomBrand == nil {
|
||||
f.CustomBrand = new(bool)
|
||||
*f.CustomBrand = true
|
||||
}
|
||||
}
|
||||
|
||||
func (l *License) IsExpired() bool {
|
||||
|
||||
@@ -114,3 +114,24 @@ func (s SqlSystemStore) Get() StoreChannel {
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlSystemStore) GetByName(name string) StoreChannel {
|
||||
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
var system model.System
|
||||
if err := s.GetReplica().SelectOne(&system, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
|
||||
result.Err = model.NewLocAppError("SqlSystemStore.GetByName", "store.sql_system.get_by_name.app_error", nil, "")
|
||||
}
|
||||
|
||||
result.Data = &system
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
@@ -30,6 +30,12 @@ func TestSqlSystemStore(t *testing.T) {
|
||||
if systems2[system.Name] != system.Value {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
result3 := <-store.System().GetByName(system.Name)
|
||||
rsystem := result3.Data.(*model.System)
|
||||
if rsystem.Value != system.Value {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlSystemStoreSaveOrUpdate(t *testing.T) {
|
||||
|
||||
@@ -184,6 +184,7 @@ type SystemStore interface {
|
||||
SaveOrUpdate(system *model.System) StoreChannel
|
||||
Update(system *model.System) StoreChannel
|
||||
Get() StoreChannel
|
||||
GetByName(name string) StoreChannel
|
||||
}
|
||||
|
||||
type WebhookStore interface {
|
||||
|
||||
@@ -201,6 +201,8 @@ func getClientConfig(c *model.Config) map[string]string {
|
||||
props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation)
|
||||
props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames)
|
||||
props["EnableTeamListing"] = strconv.FormatBool(*c.TeamSettings.EnableTeamListing)
|
||||
props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand)
|
||||
props["CustomBrandText"] = *c.TeamSettings.CustomBrandText
|
||||
|
||||
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ func getClientLicense(l *model.License) map[string]string {
|
||||
props["MFA"] = strconv.FormatBool(*l.Features.MFA)
|
||||
props["GoogleSSO"] = strconv.FormatBool(*l.Features.GoogleSSO)
|
||||
props["Compliance"] = strconv.FormatBool(*l.Features.Compliance)
|
||||
props["CustomBrand"] = strconv.FormatBool(*l.Features.CustomBrand)
|
||||
props["IssuedAt"] = strconv.FormatInt(l.IssuedAt, 10)
|
||||
props["StartsAt"] = strconv.FormatInt(l.StartsAt, 10)
|
||||
props["ExpiresAt"] = strconv.FormatInt(l.ExpiresAt, 10)
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import ReactDOM from 'react-dom';
|
||||
import * as Client from 'utils/client.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
siteNameExample: {
|
||||
@@ -29,42 +29,91 @@ const holders = defineMessages({
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const ENABLE_BRAND_ACTION = 'enable_brand_action';
|
||||
const DISABLE_BRAND_ACTION = 'disable_brand_action';
|
||||
|
||||
class TeamSettings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleImageChange = this.handleImageChange.bind(this);
|
||||
this.handleImageSubmit = this.handleImageSubmit.bind(this);
|
||||
|
||||
this.uploading = false;
|
||||
|
||||
this.state = {
|
||||
saveNeeded: false,
|
||||
brandImageExists: false,
|
||||
enableCustomBrand: this.props.config.TeamSettings.EnableCustomBrand,
|
||||
serverError: null
|
||||
};
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
||||
componentWillMount() {
|
||||
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
|
||||
$.get('/api/v1/admin/get_brand_image').done(() => this.setState({brandImageExists: true}));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.refs.image) {
|
||||
const reader = new FileReader();
|
||||
|
||||
const img = this.refs.image;
|
||||
reader.onload = (e) => {
|
||||
$(img).attr('src', e.target.result);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(this.state.brandImage);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(action) {
|
||||
var s = {saveNeeded: true};
|
||||
|
||||
if (action === ENABLE_BRAND_ACTION) {
|
||||
s.enableCustomBrand = true;
|
||||
}
|
||||
|
||||
if (action === DISABLE_BRAND_ACTION) {
|
||||
s.enableCustomBrand = false;
|
||||
}
|
||||
|
||||
this.setState(s);
|
||||
}
|
||||
|
||||
handleImageChange() {
|
||||
const element = $(this.refs.fileInput);
|
||||
if (element.prop('files').length > 0) {
|
||||
this.setState({fileSelected: true, brandImage: element.prop('files')[0]});
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
$('#save-button').button('loading');
|
||||
|
||||
var config = this.props.config;
|
||||
config.TeamSettings.SiteName = ReactDOM.findDOMNode(this.refs.SiteName).value.trim();
|
||||
config.TeamSettings.RestrictCreationToDomains = ReactDOM.findDOMNode(this.refs.RestrictCreationToDomains).value.trim();
|
||||
config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked;
|
||||
config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked;
|
||||
config.TeamSettings.RestrictTeamNames = ReactDOM.findDOMNode(this.refs.RestrictTeamNames).checked;
|
||||
config.TeamSettings.EnableTeamListing = ReactDOM.findDOMNode(this.refs.EnableTeamListing).checked;
|
||||
config.TeamSettings.SiteName = this.refs.SiteName.value.trim();
|
||||
config.TeamSettings.RestrictCreationToDomains = this.refs.RestrictCreationToDomains.value.trim();
|
||||
config.TeamSettings.EnableTeamCreation = this.refs.EnableTeamCreation.checked;
|
||||
config.TeamSettings.EnableUserCreation = this.refs.EnableUserCreation.checked;
|
||||
config.TeamSettings.RestrictTeamNames = this.refs.RestrictTeamNames.checked;
|
||||
config.TeamSettings.EnableTeamListing = this.refs.EnableTeamListing.checked;
|
||||
config.TeamSettings.EnableCustomBrand = this.refs.EnableCustomBrand.checked;
|
||||
|
||||
if (this.refs.CustomBrandText) {
|
||||
config.TeamSettings.CustomBrandText = this.refs.CustomBrandText.value;
|
||||
}
|
||||
|
||||
var MaxUsersPerTeam = 50;
|
||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) {
|
||||
MaxUsersPerTeam = parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10);
|
||||
if (!isNaN(parseInt(this.refs.MaxUsersPerTeam.value, 10))) {
|
||||
MaxUsersPerTeam = parseInt(this.refs.MaxUsersPerTeam.value, 10);
|
||||
}
|
||||
config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam;
|
||||
ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam;
|
||||
this.refs.MaxUsersPerTeam.value = MaxUsersPerTeam;
|
||||
|
||||
Client.saveConfig(
|
||||
config,
|
||||
@@ -86,6 +135,219 @@ class TeamSettings extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
handleImageSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.state.brandImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.uploading) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#upload-button').button('loading');
|
||||
this.uploading = true;
|
||||
|
||||
Client.uploadBrandImage(this.state.brandImage,
|
||||
() => {
|
||||
$('#upload-button').button('complete');
|
||||
this.setState({brandImageExists: true, brandImage: null});
|
||||
this.uploading = false;
|
||||
},
|
||||
(err) => {
|
||||
$('#upload-button').button('reset');
|
||||
this.uploading = false;
|
||||
this.setState({serverImageError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createBrandSettings() {
|
||||
var btnClass = 'btn';
|
||||
if (this.state.fileSelected) {
|
||||
btnClass = 'btn btn-primary';
|
||||
}
|
||||
|
||||
var serverImageError = '';
|
||||
if (this.state.serverImageError) {
|
||||
serverImageError = <div className='form-group has-error'><label className='control-label'>{this.state.serverImageError}</label></div>;
|
||||
}
|
||||
|
||||
let uploadImage;
|
||||
let uploadText;
|
||||
if (this.state.enableCustomBrand) {
|
||||
let img;
|
||||
if (this.state.brandImage) {
|
||||
img = (
|
||||
<img
|
||||
ref='image'
|
||||
className='brand-img'
|
||||
src=''
|
||||
/>
|
||||
);
|
||||
} else if (this.state.brandImageExists) {
|
||||
img = (
|
||||
<img
|
||||
className='brand-img'
|
||||
src='/api/v1/admin/get_brand_image'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
img = (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='admin.team.noBrandImage'
|
||||
defaultMessage='No brand image uploaded'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
uploadImage = (
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='CustomBrandImage'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team.brandImageTitle'
|
||||
defaultMessage='Custom Brand Image:'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
{img}
|
||||
</div>
|
||||
<div className='col-sm-4'/>
|
||||
<div className='col-sm-8'>
|
||||
<div className='file__upload'>
|
||||
<button className='btn btn-default'>
|
||||
<FormattedMessage
|
||||
id='admin.team.chooseImage'
|
||||
defaultMessage='Choose New Image'
|
||||
/>
|
||||
</button>
|
||||
<input
|
||||
ref='fileInput'
|
||||
type='file'
|
||||
accept='.jpg,.png,.bmp'
|
||||
onChange={this.handleImageChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={btnClass}
|
||||
disabled={!this.state.fileSelected}
|
||||
onClick={this.handleImageSubmit}
|
||||
id='upload-button'
|
||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')}
|
||||
data-complete-text={'<span class=\'glyphicon glyphicon-ok\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team.upload'
|
||||
defaultMessage='Upload'
|
||||
/>
|
||||
</button>
|
||||
<br/>
|
||||
{serverImageError}
|
||||
<p className='help-text no-margin'>
|
||||
<FormattedHTMLMessage
|
||||
id='admin.team.uploadDesc'
|
||||
defaultMessage='Customize your user experience by adding a custom image to your login screen. See examples at <a href="http://docs.mattermost.com/administration/config-settings.html#custom-branding" target="_blank">docs.mattermost.com/administration/config-settings.html#custom-branding</a>.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
uploadText = (
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='CustomBrandText'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team.brandTextTitle'
|
||||
defaultMessage='Custom Brand Text:'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<textarea
|
||||
type='text'
|
||||
rows='5'
|
||||
maxLength='1024'
|
||||
className='form-control admin-textarea'
|
||||
id='CustomBrandText'
|
||||
ref='CustomBrandText'
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{this.props.config.TeamSettings.CustomBrandText}
|
||||
</textarea>
|
||||
<p className='help-text'>
|
||||
<FormattedMessage
|
||||
id='admin.team.brandTextDescription'
|
||||
defaultMessage='The custom branding Markdown-formatted text you would like to appear below your custom brand image on your login sreen.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='EnableCustomBrand'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team.brandTitle'
|
||||
defaultMessage='Enable Custom Branding: '
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnableCustomBrand'
|
||||
value='true'
|
||||
ref='EnableCustomBrand'
|
||||
defaultChecked={this.props.config.TeamSettings.EnableCustomBrand}
|
||||
onChange={this.handleChange.bind(this, ENABLE_BRAND_ACTION)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='admin.team.true'
|
||||
defaultMessage='true'
|
||||
/>
|
||||
</label>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnableCustomBrand'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.TeamSettings.EnableCustomBrand}
|
||||
onChange={this.handleChange.bind(this, DISABLE_BRAND_ACTION)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='admin.team.false'
|
||||
defaultMessage='false'
|
||||
/>
|
||||
</label>
|
||||
<p className='help-text'>
|
||||
<FormattedMessage
|
||||
id='admin.team.brandDesc'
|
||||
defaultMessage='Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{uploadImage}
|
||||
{uploadText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
var serverError = '';
|
||||
@@ -98,6 +360,11 @@ class TeamSettings extends React.Component {
|
||||
saveClass = 'btn btn-primary';
|
||||
}
|
||||
|
||||
let brand;
|
||||
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
|
||||
brand = this.createBrandSettings();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
|
||||
@@ -387,6 +654,8 @@ class TeamSettings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{brand}
|
||||
|
||||
<div className='form-group'>
|
||||
<div className='col-sm-12'>
|
||||
{serverError}
|
||||
@@ -417,4 +686,4 @@ TeamSettings.propTypes = {
|
||||
config: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(TeamSettings);
|
||||
export default injectIntl(TeamSettings);
|
||||
|
||||
@@ -9,6 +9,7 @@ import LoginMfa from './components/login_mfa.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Client from 'utils/client.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
@@ -134,6 +135,24 @@ export default class Login extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
createCustomLogin() {
|
||||
if (global.window.mm_license.IsLicensed === 'true' &&
|
||||
global.window.mm_license.CustomBrand === 'true' &&
|
||||
global.window.mm_config.EnableCustomBrand === 'true') {
|
||||
const text = global.window.mm_config.CustomBrandText || '';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<img
|
||||
src='/api/v1/admin/get_brand_image'
|
||||
/>
|
||||
<p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
createLoginOptions(currentTeam) {
|
||||
const extraParam = Utils.getUrlParameter('extra');
|
||||
let extraBox = '';
|
||||
@@ -364,6 +383,8 @@ export default class Login extends React.Component {
|
||||
}
|
||||
|
||||
let content;
|
||||
let customContent;
|
||||
let customClass;
|
||||
if (this.state.showMfa) {
|
||||
content = (
|
||||
<LoginMfa
|
||||
@@ -375,6 +396,10 @@ export default class Login extends React.Component {
|
||||
);
|
||||
} else {
|
||||
content = this.createLoginOptions(currentTeam);
|
||||
customContent = this.createCustomLogin();
|
||||
if (customContent) {
|
||||
customClass = 'branded';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -388,24 +413,29 @@ export default class Login extends React.Component {
|
||||
</Link>
|
||||
</div>
|
||||
<div className='col-sm-12'>
|
||||
<div className='signup-team__container'>
|
||||
<h5 className='margin--less'>
|
||||
<FormattedMessage
|
||||
id='login.signTo'
|
||||
defaultMessage='Sign in to:'
|
||||
/>
|
||||
</h5>
|
||||
<h2 className='signup-team__name'>{currentTeam.display_name}</h2>
|
||||
<h2 className='signup-team__subdomain'>
|
||||
<FormattedMessage
|
||||
id='login.on'
|
||||
defaultMessage='on {siteName}'
|
||||
values={{
|
||||
siteName: global.window.mm_config.SiteName
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
{content}
|
||||
<div className={'signup-team__container ' + customClass}>
|
||||
<div className='signup__markdown'>
|
||||
{customContent}
|
||||
</div>
|
||||
<div className='signup__content'>
|
||||
<h5 className='margin--less'>
|
||||
<FormattedMessage
|
||||
id='login.signTo'
|
||||
defaultMessage='Sign in to:'
|
||||
/>
|
||||
</h5>
|
||||
<h2 className='signup-team__name'>{currentTeam.display_name}</h2>
|
||||
<h2 className='signup-team__subdomain'>
|
||||
<FormattedMessage
|
||||
id='login.on'
|
||||
defaultMessage='on {siteName}'
|
||||
values={{
|
||||
siteName: global.window.mm_config.SiteName
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -488,6 +488,17 @@
|
||||
"admin.system_analytics.activeUsers": "Active Users With Posts",
|
||||
"admin.system_analytics.title": "the System",
|
||||
"admin.system_analytics.totalPosts": "Total Posts",
|
||||
"admin.team.noBrandImage": "No brand image uploaded",
|
||||
"admin.team.brandImageTitle": "Custom Brand Image:",
|
||||
"admin.team.chooseImage": "Choose New Image",
|
||||
"admin.team.uploading": "Uploading..",
|
||||
"admin.team.uploaded": "Uploaded!",
|
||||
"admin.team.upload": "Upload",
|
||||
"admin.team.uploadDesc": "Customize your user experience by adding a custom image to your login screen. See examples at <a href='http://docs.mattermost.com/administration/config-settings.html#custom-branding' target='_blank'>docs.mattermost.com/administration/config-settings.html#custom-branding</a>.",
|
||||
"admin.team.brandTextTitle": "Custom Brand Text:",
|
||||
"admin.team.brandTextDescription": "The custom branding Markdown-formatted text you would like to appear below your custom brand image on your login sreen.",
|
||||
"admin.team.brandTitle": "Enable Custom Branding: ",
|
||||
"admin.team.brandDesc": "Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.",
|
||||
"admin.team.dirDesc": "When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.",
|
||||
"admin.team.dirTitle": "Enable Team Directory: ",
|
||||
"admin.team.false": "false",
|
||||
|
||||
@@ -33,3 +33,7 @@ fieldset {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@@ -738,16 +738,17 @@
|
||||
|
||||
.inner-wrap {
|
||||
@include single-transition(all, .5s, ease);
|
||||
&:before{
|
||||
content:"";
|
||||
|
||||
&:before {
|
||||
//Some trickery in order for the z-index transition to happen immediately on move-in and delayed on move-out.
|
||||
transition: background-color 0.5s ease, z-index 0s ease 0.5s;
|
||||
background-color: transparent;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: calc(100% + 30px);
|
||||
left: -15px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: background-color 0.5s ease, z-index 0s ease 0.5s;
|
||||
width: calc(100% + 30px);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@@ -755,9 +756,9 @@
|
||||
@include translate3d(290px, 0, 0);
|
||||
|
||||
&:before {
|
||||
background-color: rgba(0, 0, 0, .4);
|
||||
transition: background-color .5s ease;
|
||||
z-index: 9999;
|
||||
transition: background-color 0.5s ease;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
@charset 'UTF-8';
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
.signup-team__container {
|
||||
&.branded {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 380px;
|
||||
|
||||
.signup__markdown {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar--right {
|
||||
@include single-transition(all, .5s, ease);
|
||||
@include translateX(100%);
|
||||
|
||||
@@ -344,3 +344,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand-img {
|
||||
margin-bottom: 1.5em;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
@@ -10,12 +10,33 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.signup-team__container {
|
||||
margin: 0 auto;
|
||||
max-width: 380px;
|
||||
padding: 100px 0 50px;
|
||||
position: relative;
|
||||
|
||||
&.branded {
|
||||
@include display-flex;
|
||||
@include flex-direction(row);
|
||||
max-width: 900px;
|
||||
|
||||
.signup__markdown {
|
||||
@include flex(1.3 0 0);
|
||||
padding-right: 80px;
|
||||
|
||||
p {
|
||||
color: lighten($black, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.signup__content {
|
||||
@include flex(1 0 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.padding--less {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@@ -24,26 +24,6 @@ class AdminStoreClass extends EventEmitter {
|
||||
this.config = null;
|
||||
this.teams = null;
|
||||
this.complianceReports = null;
|
||||
|
||||
this.emitLogChange = this.emitLogChange.bind(this);
|
||||
this.addLogChangeListener = this.addLogChangeListener.bind(this);
|
||||
this.removeLogChangeListener = this.removeLogChangeListener.bind(this);
|
||||
|
||||
this.emitAuditChange = this.emitAuditChange.bind(this);
|
||||
this.addAuditChangeListener = this.addAuditChangeListener.bind(this);
|
||||
this.removeAuditChangeListener = this.removeAuditChangeListener.bind(this);
|
||||
|
||||
this.emitComplianceReportsChange = this.emitComplianceReportsChange.bind(this);
|
||||
this.addComplianceReportsChangeListener = this.addComplianceReportsChangeListener.bind(this);
|
||||
this.removeComplianceReportsChangeListener = this.removeComplianceReportsChangeListener.bind(this);
|
||||
|
||||
this.emitConfigChange = this.emitConfigChange.bind(this);
|
||||
this.addConfigChangeListener = this.addConfigChangeListener.bind(this);
|
||||
this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this);
|
||||
|
||||
this.emitAllTeamsChange = this.emitAllTeamsChange.bind(this);
|
||||
this.addAllTeamsChangeListener = this.addAllTeamsChangeListener.bind(this);
|
||||
this.removeAllTeamsChangeListener = this.removeAllTeamsChangeListener.bind(this);
|
||||
}
|
||||
|
||||
emitLogChange() {
|
||||
|
||||
@@ -1334,4 +1334,3 @@ export function regenCommandToken(id) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1738,3 +1738,22 @@ export function updateMfa(data, success, error) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function uploadBrandImage(image, success, error) {
|
||||
const formData = new FormData();
|
||||
formData.append('image', image, image.name);
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/upload_brand_image',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('uploadBrandImage', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user