From 9d9eaec14f4e4a8bc3bda4ef19c980bef2bb467b Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 6 Jan 2016 14:18:05 -0800 Subject: [PATCH 01/61] Removed ability to drag n' drop text; drag n' drop ui no longer appears when non-files are dragged --- web/react/components/create_comment.jsx | 7 ----- web/react/components/create_post.jsx | 7 ----- web/react/components/file_upload.jsx | 34 +++++++++++++++++-------- web/react/utils/utils.jsx | 6 +++++ 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index c190b4dd89..cae94429c1 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -32,7 +32,6 @@ export default class CreateComment extends React.Component { this.handleUploadStart = this.handleUploadStart.bind(this); this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); this.handleUploadError = this.handleUploadError.bind(this); - this.handleTextDrop = this.handleTextDrop.bind(this); this.removePreview = this.removePreview.bind(this); this.getFileCount = this.getFileCount.bind(this); this.handleResize = this.handleResize.bind(this); @@ -239,11 +238,6 @@ export default class CreateComment extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); } } - handleTextDrop(text) { - const newText = this.state.messageText + text; - this.handleUserInput(newText); - Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length); - } removePreview(id) { let previews = this.state.previews; let uploadsInProgress = this.state.uploadsInProgress; @@ -344,7 +338,6 @@ export default class CreateComment extends React.Component { onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} onUploadError={this.handleUploadError} - onTextDrop={this.handleTextDrop} postType='comment' channelId={this.props.channelId} /> diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index e901b272a4..a476863a3b 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -40,7 +40,6 @@ export default class CreatePost extends React.Component { this.handleUploadStart = this.handleUploadStart.bind(this); this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); this.handleUploadError = this.handleUploadError.bind(this); - this.handleTextDrop = this.handleTextDrop.bind(this); this.removePreview = this.removePreview.bind(this); this.onChange = this.onChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); @@ -281,11 +280,6 @@ export default class CreatePost extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: message}); } } - handleTextDrop(text) { - const newText = this.state.messageText + text; - this.handleUserInput(newText); - Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length); - } removePreview(id) { const previews = Object.assign([], this.state.previews); const uploadsInProgress = this.state.uploadsInProgress; @@ -462,7 +456,6 @@ export default class CreatePost extends React.Component { onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} onUploadError={this.handleUploadError} - onTextDrop={this.handleTextDrop} postType='post' channelId='' /> diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 9316ca9a59..8c7353e86e 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -109,8 +109,6 @@ export default class FileUpload extends React.Component { if (typeof files !== 'string' && files.length) { this.uploadFiles(files); - } else { - this.props.onTextDrop(e.originalEvent.dataTransfer.getData('Text')); } } @@ -120,11 +118,19 @@ export default class FileUpload extends React.Component { if (this.props.postType === 'post') { $('.row.main').dragster({ - enter() { - $('.center-file-overlay').removeClass('hidden'); + enter(dragsterEvent, e) { + var files = e.originalEvent.dataTransfer; + + if (utils.isFileTransfer(files)) { + $('.center-file-overlay').removeClass('hidden'); + } }, - leave() { - $('.center-file-overlay').addClass('hidden'); + leave(dragsterEvent, e) { + var files = e.originalEvent.dataTransfer; + + if (utils.isFileTransfer(files)) { + $('.center-file-overlay').addClass('hidden'); + } }, drop(dragsterEvent, e) { $('.center-file-overlay').addClass('hidden'); @@ -133,11 +139,19 @@ export default class FileUpload extends React.Component { }); } else if (this.props.postType === 'comment') { $('.post-right__container').dragster({ - enter() { - $('.right-file-overlay').removeClass('hidden'); + enter(dragsterEvent, e) { + var files = e.originalEvent.dataTransfer; + + if (utils.isFileTransfer(files)) { + $('.right-file-overlay').removeClass('hidden'); + } }, - leave() { - $('.right-file-overlay').addClass('hidden'); + leave(dragsterEvent, e) { + var files = e.originalEvent.dataTransfer; + + if (utils.isFileTransfer(files)) { + $('.right-file-overlay').addClass('hidden'); + } }, drop(dragsterEvent, e) { $('.right-file-overlay').addClass('hidden'); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 33aae7d1e9..95eca7c3a1 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -1276,3 +1276,9 @@ export function fillArray(value, length) { return arr; } + +// Checks if a data transfer contains files not text, folders, etc.. +// Slightly modified from http://stackoverflow.com/questions/6848043/how-do-i-detect-a-file-is-being-dragged-rather-than-a-draggable-element-on-my-pa +export function isFileTransfer(files) { + return files.types != null && (files.types.indexOf ? files.types.indexOf('Files') !== -1 : files.types.contains('application/x-moz-file')); +} From 0e7a149982f73919badd9d366d06fa699925c89f Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 6 Jan 2016 18:04:24 -0500 Subject: [PATCH 02/61] Added the ability to get more channel members from getChannelExtraInfo --- api/channel.go | 21 ++++++++++++--- api/channel_benchmark_test.go | 2 +- api/channel_test.go | 48 ++++++++++++++++++++++++++++++----- model/channel.go | 4 +-- model/client.go | 4 +-- store/sql_channel_store.go | 9 ++++++- 6 files changed, 73 insertions(+), 15 deletions(-) diff --git a/api/channel.go b/api/channel.go index b85de30719..674293e192 100644 --- a/api/channel.go +++ b/api/channel.go @@ -9,9 +9,14 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/platform/model" "net/http" + "strconv" "strings" ) +const ( + defaultExtraMemberLimit = 100 +) + func InitChannel(r *mux.Router) { l4g.Debug("Initializing channel api routes") @@ -27,6 +32,7 @@ func InitChannel(r *mux.Router) { sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET") + sr.Handle("/{id:[A-Za-z0-9]+}/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(join)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/leave", ApiUserRequired(leave)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/delete", ApiUserRequired(deleteChannel)).Methods("POST") @@ -730,10 +736,19 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) { } func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) id := params["id"] + var memberLimit int + if memberLimitString, ok := params["member_limit"]; !ok { + memberLimit = defaultExtraMemberLimit + } else if memberLimitInt64, err := strconv.ParseInt(memberLimitString, 10, 0); err != nil { + c.Err = model.NewAppError("getChannelExtraInfo", "Failed to parse member limit", err.Error()) + return + } else { + memberLimit = int(memberLimitInt64) + } + sc := Srv.Store.Channel().Get(id) var channel *model.Channel if cresult := <-sc; cresult.Err != nil { @@ -743,13 +758,13 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { channel = cresult.Data.(*model.Channel) } - extraEtag := channel.ExtraEtag() + extraEtag := channel.ExtraEtag(memberLimit) if HandleEtag(extraEtag, w, r) { return } scm := Srv.Store.Channel().GetMember(id, c.Session.UserId) - ecm := Srv.Store.Channel().GetExtraMembers(id, 100) + ecm := Srv.Store.Channel().GetExtraMembers(id, memberLimit) ccm := Srv.Store.Channel().GetMemberCount(id) if cmresult := <-scm; cmresult.Err != nil { diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go index fb8dd61bc9..d6e1e5a556 100644 --- a/api/channel_benchmark_test.go +++ b/api/channel_benchmark_test.go @@ -189,7 +189,7 @@ func BenchmarkGetChannelExtraInfo(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { for j := range channels { - Client.Must(Client.GetChannelExtraInfo(channels[j].Id, "")) + Client.Must(Client.GetChannelExtraInfo(channels[j].Id, -1, "")) } } } diff --git a/api/channel_test.go b/api/channel_test.go index 4ef164cba8..1172783785 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -674,7 +674,7 @@ func TestGetChannelExtraInfo(t *testing.T) { channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, "")) + rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, -1, "")) data := rget.Data.(*model.ChannelExtra) if data.Id != channel1.Id { t.Fatal("couldnt't get extra info") @@ -690,7 +690,7 @@ func TestGetChannelExtraInfo(t *testing.T) { currentEtag := rget.Etag - if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil { + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { t.Fatal(err) } else if cache_result.Data.(*model.ChannelExtra) != nil { t.Log(cache_result.Data) @@ -708,7 +708,7 @@ func TestGetChannelExtraInfo(t *testing.T) { Client2.LoginByEmail(team.Name, user2.Email, "pwd") Client2.Must(Client2.JoinChannel(channel1.Id)) - if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil { + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { t.Fatal(err) } else if cache_result.Data.(*model.ChannelExtra) == nil { t.Log(cache_result.Data) @@ -717,7 +717,7 @@ func TestGetChannelExtraInfo(t *testing.T) { currentEtag = cache_result.Etag } - if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil { + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { t.Fatal(err) } else if cache_result.Data.(*model.ChannelExtra) != nil { t.Log(cache_result.Data) @@ -728,7 +728,7 @@ func TestGetChannelExtraInfo(t *testing.T) { Client2.Must(Client2.LeaveChannel(channel1.Id)) - if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil { + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { t.Fatal(err) } else if cache_result.Data.(*model.ChannelExtra) == nil { t.Log(cache_result.Data) @@ -737,7 +737,7 @@ func TestGetChannelExtraInfo(t *testing.T) { currentEtag = cache_result.Etag } - if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil { + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { t.Fatal(err) } else if cache_result.Data.(*model.ChannelExtra) != nil { t.Log(cache_result.Data) @@ -745,6 +745,42 @@ func TestGetChannelExtraInfo(t *testing.T) { } else { currentEtag = cache_result.Etag } + + Client2.Must(Client2.JoinChannel(channel1.Id)) + + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 2, currentEtag); err != nil { + t.Fatal(err) + } else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil { + t.Fatal("response should not be empty") + } else if len(extra.Members) != 2 { + t.Fatal("should've returned 2 members") + } else if extra.MemberCount != 2 { + t.Fatal("should've returned member count of 2") + } else { + currentEtag = cache_result.Etag + } + + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil { + t.Fatal(err) + } else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil { + t.Fatal("response should not be empty") + } else if len(extra.Members) != 1 { + t.Fatal("should've returned only 1 member") + } else if extra.MemberCount != 2 { + t.Fatal("should've returned member count of 2") + } else { + currentEtag = cache_result.Etag + } + + if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil { + t.Fatal(err) + } else if cache_result.Data.(*model.ChannelExtra) != nil { + t.Log(cache_result.Data) + t.Fatal("response should be empty") + } else { + currentEtag = cache_result.Etag + } + } func TestAddChannelMember(t *testing.T) { diff --git a/model/channel.go b/model/channel.go index 0ce09f4bca..7109500d4d 100644 --- a/model/channel.go +++ b/model/channel.go @@ -57,8 +57,8 @@ func (o *Channel) Etag() string { return Etag(o.Id, o.UpdateAt) } -func (o *Channel) ExtraEtag() string { - return Etag(o.Id, o.ExtraUpdateAt) +func (o *Channel) ExtraEtag(memberLimit int) string { + return Etag(o.Id, o.ExtraUpdateAt, memberLimit) } func (o *Channel) IsValid() *AppError { diff --git a/model/client.go b/model/client.go index f1773f3c7a..14746f8ae6 100644 --- a/model/client.go +++ b/model/client.go @@ -591,8 +591,8 @@ func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) { } } -func (c *Client) GetChannelExtraInfo(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/channels/"+id+"/extra_info", "", etag); err != nil { +func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet("/channels/"+id+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index c9e0d113f9..4585647de8 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -603,7 +603,14 @@ func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChann result := StoreResult{} var members []model.ExtraMember - _, err := s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit}) + var err error + + if limit != -1 { + _, err = s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit}) + } else { + _, err = s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}) + } + if err != nil { result.Err = model.NewAppError("SqlChannelStore.GetExtraMembers", "We couldn't get the extra info for channel members", "channel_id="+channelId+", "+err.Error()) } else { From d9f5e0097b28ba5efa105fea3c7da0bfbeb49070 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 7 Jan 2016 13:07:22 +0500 Subject: [PATCH 03/61] Multiple UI Improvements --- web/react/components/file_attachment.jsx | 2 +- web/react/components/post_info.jsx | 2 +- web/react/components/posts_view.jsx | 7 ++++++- web/sass-files/sass/partials/_post.scss | 6 ++++-- web/sass-files/sass/partials/_post_right.scss | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index c102696804..eeb218bfec 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -266,7 +266,7 @@ export default class FileAttachment extends React.Component { href={fileUrl} download={filenameString} data-toggle='tooltip' - title={'Download ' + filenameString} + title={'Download \"' + filenameString + '\"'} className='post-image__name' > {trimmedFilename} diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 21683bb01e..26bd6addef 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -223,13 +223,13 @@ export default class PostInfo extends React.Component { />
  • - {comments}
    {dropdown}
    + {comments} ReactDOM.findDOMNode(this.refs.dotMenu)} diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index a28efbd041..41262f2da9 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -24,6 +24,7 @@ export default class PostsView extends React.Component { this.updateScrolling = this.updateScrolling.bind(this); this.handleResize = this.handleResize.bind(this); this.scrollToBottom = this.scrollToBottom.bind(this); + this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this); this.jumpToPostNode = null; this.wasAtBottom = true; @@ -339,6 +340,10 @@ export default class PostsView extends React.Component { this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; }); } + scrollToBottomAnimated() { + var postList = $(this.refs.postlist); + postList.animate({scrollTop:this.refs.postlist.scrollHeight}, '500'); + } componentDidMount() { if (this.props.postList != null) { this.updateScrolling(); @@ -458,7 +463,7 @@ export default class PostsView extends React.Component {
    Date: Thu, 7 Jan 2016 10:25:56 -0500 Subject: [PATCH 04/61] Made ChannelInviteModal pull all channel members before rendering --- web/react/components/channel_invite_modal.jsx | 33 ++++++++++++++----- web/react/utils/async_client.jsx | 3 +- web/react/utils/client.jsx | 11 +++++-- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index 7dac399420..8b7485e5fe 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -20,9 +20,14 @@ export default class ChannelInviteModal extends React.Component { this.onListenerChange = this.onListenerChange.bind(this); this.handleInvite = this.handleInvite.bind(this); - this.state = this.getStateFromStores(); + // the state gets populated when the modal is shown + this.state = {}; } shouldComponentUpdate(nextProps, nextState) { + if (!this.props.show && !nextProps.show) { + return false; + } + if (!Utils.areObjectsEqual(this.props, nextProps)) { return true; } @@ -34,13 +39,25 @@ export default class ChannelInviteModal extends React.Component { return false; } getStateFromStores() { - function getId(user) { - return user.id; - } - var users = UserStore.getActiveOnlyProfiles(); - var memberIds = ChannelStore.getCurrentExtraInfo().members.map(getId); + const users = UserStore.getActiveOnlyProfiles(); - var loading = $.isEmptyObject(users); + if ($.isEmptyObject(users)) { + return { + loading: true + }; + } + + // make sure we have all members of this channel before rendering + const extraInfo = ChannelStore.getCurrentExtraInfo(); + if (extraInfo.member_count !== extraInfo.members.length) { + AsyncClient.getChannelExtraInfo(this.props.channel.id, -1); + + return { + loading: true + }; + } + + const memberIds = extraInfo.members.map((user) => user.id); var nonmembers = []; for (var id in users) { @@ -55,7 +72,7 @@ export default class ChannelInviteModal extends React.Component { return { nonmembers, - loading + loading: false }; } onShow() { diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index f218270da0..0ee89b9fad 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -168,7 +168,7 @@ export function getMoreChannels(force) { } } -export function getChannelExtraInfo(id) { +export function getChannelExtraInfo(id, memberLimit) { let channelId; if (id) { channelId = id; @@ -185,6 +185,7 @@ export function getChannelExtraInfo(id) { client.getChannelExtraInfo( channelId, + memberLimit, (data, textStatus, xhr) => { callTracker['getChannelExtraInfo_' + channelId] = 0; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index e1c331aff6..96d1ef720f 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -824,10 +824,17 @@ export function getChannelCounts(success, error) { }); } -export function getChannelExtraInfo(id, success, error) { +export function getChannelExtraInfo(id, memberLimit, success, error) { + let url = '/api/v1/channels/' + id + '/extra_info'; + + if (memberLimit) { + url += '/' + memberLimit; + } + $.ajax({ - url: '/api/v1/channels/' + id + '/extra_info', + url, dataType: 'json', + contentType: 'application/json', type: 'GET', success, error: function onError(xhr, status, err) { From d7230e8753c848725ba5b61d11b29b6280fce94b Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 7 Jan 2016 10:26:43 -0500 Subject: [PATCH 05/61] Added a loading screen to ChannelMembersModal and made it pull all channel members before rendering --- .../components/channel_members_modal.jsx | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx index d1b9df9885..513a720e7d 100644 --- a/web/react/components/channel_members_modal.jsx +++ b/web/react/components/channel_members_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import LoadingScreen from './loading_screen.jsx'; import MemberList from './member_list.jsx'; import ChannelInviteModal from './channel_invite_modal.jsx'; @@ -21,9 +22,10 @@ export default class ChannelMembersModal extends React.Component { this.onChange = this.onChange.bind(this); this.handleRemove = this.handleRemove.bind(this); - const state = this.getStateFromStores(); - state.showInviteModal = false; - this.state = state; + // the rest of the state gets populated when the modal is shown + this.state = { + showInviteModal: false + }; } shouldComponentUpdate(nextProps, nextState) { if (!Utils.areObjectsEqual(this.props, nextProps)) { @@ -37,8 +39,18 @@ export default class ChannelMembersModal extends React.Component { return false; } getStateFromStores() { + const extraInfo = ChannelStore.getCurrentExtraInfo(); + + if (extraInfo.member_count !== extraInfo.members.length) { + AsyncClient.getChannelExtraInfo(this.props.channel.id, -1); + + return { + loading: true + }; + } + const users = UserStore.getActiveOnlyProfiles(); - const memberList = ChannelStore.getCurrentExtraInfo().members; + const memberList = extraInfo.members; const nonmemberList = []; for (const id in users) { @@ -71,14 +83,14 @@ export default class ChannelMembersModal extends React.Component { return { nonmemberList, - memberList + memberList, + loading: false }; } onShow() { if ($(window).width() > 768) { $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); } - this.onChange(); } componentDidUpdate(prevProps) { if (this.props.show && !prevProps.show) { @@ -89,6 +101,8 @@ export default class ChannelMembersModal extends React.Component { if (!this.props.show && nextProps.show) { ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange); + + this.onChange(); } else if (this.props.show && !nextProps.show) { ChannelStore.removeExtraInfoChangeListener(this.onChange); ChannelStore.removeChangeListener(this.onChange); @@ -154,6 +168,21 @@ export default class ChannelMembersModal extends React.Component { isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles); } + let content; + if (this.state.loading) { + content = (); + } else { + content = ( +
    + +
    + ); + } + return (
    -
    - -
    + {content} + >{'Add another'}

    - People invited automatically join Town Square channel. + {'People invited automatically join the '}{defaultChannelName}{' channel.'}
    ); From 2d6ebec4619f1db03b579c982608239d45ef6e70 Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 7 Jan 2016 13:44:02 -0800 Subject: [PATCH 16/61] Adding highlights, minor tweaks --- CHANGELOG.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b984b372..7338f2378c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,28 @@ Expected Release date: 2016-01-16 ### Release Highlights +#### Data Center Support + +- Deployment guides on Red Hat Enterprise Linux 6 and 7 now available +- Legal disclosure and support links (terms of service, privacy policy, help, about, and support email) now configurable +- Over a dozen new configuration options in System Console + +#### Mobile Experience + +- iOS reference app [now available from iTunes](https://itunes.apple.com/us/app/mattermost/id984966508?ls=1&mt=8), compiled from [open source repo](https://github.com/mattermost/ios) +- Date headers now show when scrolling on mobile, so you can quickly see when messages were sent +- Added "rapid scroll" support for jumping quickily to bottom of channels on mobile + ### New Features +Mobile Experience +- Date headers now show when scrolling on mobile, so you can quickly see when messages were sent +- Added "rapid scroll" support for jumping quickily to bottom of channels on mobile + Authentication -- Added ability to switch between email sign in and SSO provider -- Added fields to the config file to make session token length configurable +- Accounts can now switch between email and GitLab SSO sign-in options +- New ability to customize session token length System Console @@ -21,24 +37,24 @@ System Console Performance and Testing -- Added more debugging info for email and push notifications +- Added logging for email and push notifications events in DEBUG mode Integrations -- Added support to allow optional parameters in the Content-Type of incoming webhook requests +- Added support to allow optional parameters in the `Content-Type` of incoming webhook requests Files and Images -- GIFs autoplay in the image previewer +- Animated GIFs autoplay in the image previewer Notifications and Email -- Changed email notifications to use the server's local timezone instead of UTC +- Changed email notifications to display the server's local timezone instead of UTC User Interface - Updated the "About Mattermost" dialog formatting -- Going to domain/teamname now to the last channel of your previous session, instead of Town Square +- Going to domain/teamname now goes to the last channel of your previous session, instead of Town Square - Various improvements to mobile UI, including a floating date indicator and the ability to quickly scroll to the bottom of the channel #### Bug Fixes @@ -84,7 +100,7 @@ Multiple settings were added to `config.json`. These options can be modified in - No scroll bar in center channel - Pasting images into text box fails to upload on Firefox, Safari, and IE11 - Public links for attachments attempt to download the file on IE, Edge, and Safari -- Slack import @mentions break +- Importing from Slack breaks @mentions and fails to load in certain cases with comments on files - System Console > TEAMS > Statistics > Newly Created Users shows all of the users are created "just now" - Favicon does not always become red when @mentions and direct messages are received on an inactive browser tab - Searching for a phrase in quotations returns more than just the phrase on Mattermost installations with a Postgres database @@ -98,13 +114,13 @@ Multiple settings were added to `config.json`. These options can be modified in Many thanks to our external contributors. In no particular order: +- [npcode](https://github.com/npcode) +- [hjf288](https://github.com/hjf288) - [apskim](https://github.com/apskim) - [ejm2172](https://github.com/ejm2172) -- [hjf288](https://github.com/hjf288) - [hvnsweeting](https://github.com/hvnsweeting) - [benburkert](https://github.com/benburkert) - [erikthered](https://github.com/erikthered) -- [npcode](https://github.com/npcode) ## Release v1.3.0 From 05023fdc3afbf99c1907c94b4848398925d79140 Mon Sep 17 00:00:00 2001 From: lfbrock Date: Thu, 7 Jan 2016 16:54:16 -0500 Subject: [PATCH 17/61] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd15a723d..7300e58e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Authentication System Console - Added "Legal and Support Settings" so System Administrators can change the default Terms of Service, Privacy Policy, and Help links +- Added settings for web, mobile, and SSO session lengths under "Service Settings" ### Improvements From 7f1f7885e73b2e54b590adc2207d807cf72b3843 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Fri, 8 Jan 2016 12:01:20 +0500 Subject: [PATCH 18/61] Updating fix --- web/react/components/posts_view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 41262f2da9..7d8c7e2659 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -342,7 +342,7 @@ export default class PostsView extends React.Component { } scrollToBottomAnimated() { var postList = $(this.refs.postlist); - postList.animate({scrollTop:this.refs.postlist.scrollHeight}, '500'); + postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500'); } componentDidMount() { if (this.props.postList != null) { From c2f7aadfa7fad3e6058af5c3be6d40d48727d8ac Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 8 Jan 2016 09:25:44 -0500 Subject: [PATCH 19/61] Fixed FileUpload to properly unbind drag handlers when unmounted --- web/react/components/file_upload.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 8c7353e86e..a0c930ffb7 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -243,6 +243,18 @@ export default class FileUpload extends React.Component { }); } + componentWillUnmount() { + let target; + if (this.props.postType === 'post') { + target = $('.row.main'); + } else { + target = $('.post-right__container'); + } + + // jquery-dragster doesn't provide a function to unregister itself so do it manually + target.off('dragenter dragleave dragover drop dragster:enter dragster:leave dragster:over dragster:drop'); + } + cancelUpload(clientId) { var requests = this.state.requests; var request = requests[clientId]; From 85111e7098617514d66f08d1e35102512a22060f Mon Sep 17 00:00:00 2001 From: lfbrock Date: Fri, 8 Jan 2016 10:01:45 -0500 Subject: [PATCH 20/61] Create test-search.md --- doc/developer/tests/test-search.md | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 doc/developer/tests/test-search.md diff --git a/doc/developer/tests/test-search.md b/doc/developer/tests/test-search.md new file mode 100644 index 0000000000..0f0ba11534 --- /dev/null +++ b/doc/developer/tests/test-search.md @@ -0,0 +1,43 @@ +# Search Testing + +### Basic Search Testing + +This post is used by the core team to test search. It should be returned for the test cases for search, with proper highlighting in the search results. + +**Basic word search:** Hello world! +**Emoji search:** :strawberry: +**Accent search:** Crème friache +**Non-latin search:** +您好吗 +您好 +**Email search:** person@domain.org +**Link search:** www.dropbox.com +**Markdown search:** +##### Hello +``` +Hello +``` +`Hello` + + +### Hashtags: + +Click on the linked hashtags below, and confirm that the search results match the linked hashtags. Confirm that the highlighting in the search results is correct. + +#### Basic Hashtags + +#hello #world + +#### Hashtags containing punctuation: + +*Note: Make a separate post containing only the word “hashtag”, and confirm the hashtags below do not return the separate post.* + +#hashtag #hashtag-dash #hashtag_underscore #hashtag.dot + +#### Punctuation following a hashtag: + +#colon: #slash/ #backslash\ #percent% #dollar$ #semicolon; #ampersand& #bracket( #bracket) #lessthan< #greaterthan> #dash- #plus+ #equals= #caret^ #hashtag# #asterisk* #verticalbar| #invertedquestion¿ #atsign@ #quote” #apostrophe' #curlybracket{ #curlybracket} #squarebracket[ #squarebracket] + +#### Markdown surrounding a hashtag: + +*#markdown-hashtag* From 1d43c1c26f08e174fbebfb6f1c5de56595013bbf Mon Sep 17 00:00:00 2001 From: lfbrock Date: Fri, 8 Jan 2016 11:00:17 -0500 Subject: [PATCH 21/61] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7300e58e67..180b90653b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,8 @@ Authentication System Console -- Added "Legal and Support Settings" so System Administrators can change the default Terms of Service, Privacy Policy, and Help links -- Added settings for web, mobile, and SSO session lengths under "Service Settings" +- Added *Legal and Support Settings* so System Administrators can change the default Terms of Service, Privacy Policy, and Help links +- Under *Service Settings* added options to customize expiry of web, mobile and SSO session tokens, expiry of caches in memory, and an EnableDeveloper option to turn on Developer Mode which alerts users to any console errors that occur ### Improvements From 3b3a5096060a19e7ba7a6ceec12ccc42c1269663 Mon Sep 17 00:00:00 2001 From: lfbrock Date: Fri, 8 Jan 2016 11:00:43 -0500 Subject: [PATCH 22/61] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180b90653b..afdf62d9ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,8 @@ Authentication System Console -- Added *Legal and Support Settings* so System Administrators can change the default Terms of Service, Privacy Policy, and Help links -- Under *Service Settings* added options to customize expiry of web, mobile and SSO session tokens, expiry of caches in memory, and an EnableDeveloper option to turn on Developer Mode which alerts users to any console errors that occur +- Added **Legal and Support Settings** so System Administrators can change the default Terms of Service, Privacy Policy, and Help links +- Under **Service Settings** added options to customize expiry of web, mobile and SSO session tokens, expiry of caches in memory, and an EnableDeveloper option to turn on Developer Mode which alerts users to any console errors that occur ### Improvements From 4e6afaa5fa726c0a639da8c82415358896023ebf Mon Sep 17 00:00:00 2001 From: lfbrock Date: Fri, 8 Jan 2016 11:19:34 -0500 Subject: [PATCH 23/61] Update CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afdf62d9ec..42b4857421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,9 @@ User Interface #### Config.json Changes from v1.3 to v1.4 -Multiple settings were added to `config.json`. These options can be modified in the System Console, or manually updated in the existing config.json file. This is a list of changes and their new default values in a fresh install: +Multiple settings were added to `config.json`. Below is a list of the changes and their new default values in a fresh install. + +The following options can be modified in the System Console: - Under `ServiceSettings` in `config.json`: - Added: `"EnableDeveloper": false` to set whether developer mode is enabled, which alerts users to any console errors that occur @@ -81,11 +83,6 @@ Multiple settings were added to `config.json`. These options can be modified in - Added: `"SessionLengthMobileInDays" : 30` to set the number of days before native mobile sessions expire - Added: `"SessionLengthSSOInDays" : 30` to set the number of days before SSO sessions expire - Added: `"SessionCacheInMinutes" : 10` to set the number of minutes to cache a session in memory -- Under `FileSettings` in `config.json`: - - Added: `"AmazonS3Endpoint": ""` to set an endpoint URL for an Amazon S3 instance - - Added: `"AmazonS3BucketEndpoint": ""` to set an endpoint URL for Amazon S3 buckets - - Added: `"AmazonS3LocationConstraint": false` to set whether the S3 region is location constrained - - Added: `"AmazonS3LowercaseBucket": false` to set whether bucket names are fully lowercase or not - Added `SupportSettings` section to `config.json`: - Added: `"TermsOfServiceLink": "/static/help/terms.html"` to allow System Administrators to set the terms of service link - Added: `"PrivacyPolicyLink": "/static/help/privacy.html"` to allow System Administrators to set the privacy policy link @@ -94,6 +91,14 @@ Multiple settings were added to `config.json`. These options can be modified in - Added: `"ReportAProblemLink": "/static/help/report_problem.html"` to allow System Administrators to set the home page for the support website - Added: `"SupportEmail":"feedback@mattermost.com"` to allow System Administrators to set an email address for feedback and support requests +The following options are not present in the System Console, and can be modified manually in the `config.json` file: + +- Under `FileSettings` in `config.json`: + - Added: `"AmazonS3Endpoint": ""` to set an endpoint URL for an Amazon S3 instance + - Added: `"AmazonS3BucketEndpoint": ""` to set an endpoint URL for Amazon S3 buckets + - Added: `"AmazonS3LocationConstraint": false` to set whether the S3 region is location constrained + - Added: `"AmazonS3LowercaseBucket": false` to set whether bucket names are fully lowercase or not + #### Known Issues - When navigating to a page with new messages as well as message containing inline images added via markdown, the channel may move up and down while loading the inline images From 1358d7ea296089f9dd9cdafabc7ebdfc1ddd3faf Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 12:43:01 -0600 Subject: [PATCH 24/61] Removing minified for react to track down an issues. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9fd74b959f..0dfa234728 100644 --- a/Makefile +++ b/Makefile @@ -136,11 +136,11 @@ package: mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js - sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html - sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html - sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html From 2f74fd7bf1ef1707fdb1b0a446dd403ec066eb3b Mon Sep 17 00:00:00 2001 From: Corey Hulen Date: Fri, 8 Jan 2016 13:08:47 -0600 Subject: [PATCH 25/61] Revert "Removing minified for react to track down an issues." --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0dfa234728..9fd74b959f 100644 --- a/Makefile +++ b/Makefile @@ -136,11 +136,11 @@ package: mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js - #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html - #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html - #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html From 267219af58be4623fd53e549746392e8d7edacfb Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 13:35:11 -0600 Subject: [PATCH 26/61] Adding debugging for react --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9fd74b959f..1d7fbea5a3 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,10 @@ package: cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/ cp -RL web/templates $(DIST_PATH)/web + cp -L web/static/js/react-0.14.3.js $(DIST_PATH)/web/static/js/ + cp -L web/static/js/react-dom-0.14.3.js $(DIST_PATH)/web/static/js/ + cp -L web/static/js/react-bootstrap-0.28.1.js $(DIST_PATH)/web/static/js/ + mkdir -p $(DIST_PATH)/api cp -RL api/templates $(DIST_PATH)/api @@ -136,11 +140,11 @@ package: mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js - sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html - sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html - sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html + #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html From bfc3012e6a059dc89b69b41ab3fe1fc43f1406f5 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 9 Jan 2016 21:39:19 -0800 Subject: [PATCH 27/61] Documenting how Preview Mode turns off --- web/react/components/admin_console/email_settings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 91d73dccde..48727e6168 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -254,7 +254,7 @@ export default class EmailSettings extends React.Component { /> {'false'} -

    {'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.'}

    +

    {'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner after logging out and logging back in.'}

    From 0b1212f4745857a73a04ec6315a2d92d731f50d4 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 9 Jan 2016 21:42:18 -0800 Subject: [PATCH 28/61] Update Configuration-Settings.md --- doc/install/Configuration-Settings.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/install/Configuration-Settings.md b/doc/install/Configuration-Settings.md index 962be0eb7e..31d4551f6e 100644 --- a/doc/install/Configuration-Settings.md +++ b/doc/install/Configuration-Settings.md @@ -123,11 +123,13 @@ Settings to configure email signup, notifications, security, and SMTP options. #### Notifications ```"SendEmailNotifications": false``` -"true": Enables sending of email notifications. “false”: Disables email notifications for developers who may want to skip email setup for faster development. +"true": Enables sending of email notifications. “false”: Disables email notifications for developers who may want to skip email setup for faster development. Setting this to true removes the **Preview Mode: Email notifications have not been configured** banner (requires logging out and logging back in after setting is changed) + ```"RequireEmailVerification": false``` "true": Require email verification after account creation prior to allowing login; “false”: Users do not need to verify their email address prior to login. Developers may set this field to false so skip sending verification emails for faster development. + ```"FeedbackName": ""``` Name displayed on email account used when sending notification emails from Mattermost system. From bf1f0d03191165b5735e1a4f5850971b507ec513 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 9 Jan 2016 21:43:42 -0800 Subject: [PATCH 29/61] Update email_settings.jsx --- web/react/components/admin_console/email_settings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 48727e6168..6aec3c7b1f 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -254,7 +254,7 @@ export default class EmailSettings extends React.Component { /> {'false'} -

    {'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner after logging out and logging back in.'}

    +

    {'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'}

    From a1497aa73fb43a8ba73ba102e3660bd4337d71c3 Mon Sep 17 00:00:00 2001 From: it33 Date: Sun, 10 Jan 2016 19:42:08 -0800 Subject: [PATCH 30/61] Update Upgrade-Guide.md --- doc/install/Upgrade-Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md index 4480dedd28..1f3ff95103 100644 --- a/doc/install/Upgrade-Guide.md +++ b/doc/install/Upgrade-Guide.md @@ -43,7 +43,7 @@ The following instructions apply to updating installations of Mattermost v0.7-Be Mattermost is designed to be upgraded sequentially through major version releases. If you skip versions when upgrading GitLab, you may find a `panic: The database schema version of 1.1.0 cannot be upgraded. You must not skip a version` error in your `/var/log/gitlab/mattermost/current` directory. If so: 1. Run `platform -version` to check your version of Mattermost -2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](/var/log/gitlab/mattermost/current) and using the database schema version listed in the error messages for the version you select in Step 1) iv). +2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-to-next-major-release) and using the database schema version listed in the error messages for the version you select in Step 1) iv). 3. Once Mattermost is working again, you can use the same upgrade procedure to upgrade Mattermost version by version to your current GitLab version. After this is done, GitLab automation should continue to work for future upgrades, so long as you don't skip versions. | GitLab Version | Mattermost Version | From b1251b93932adf616a996725448d0b77fad0d3c1 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 09:12:51 -0600 Subject: [PATCH 31/61] Upgrade logging package --- api/admin.go | 2 +- api/api.go | 2 +- api/channel.go | 2 +- api/command.go | 2 +- api/context.go | 2 +- api/file.go | 2 +- api/import.go | 2 +- api/oauth.go | 2 +- api/post.go | 2 +- api/preference.go | 2 +- api/server.go | 2 +- api/slackimport.go | 2 +- api/team.go | 2 +- api/user.go | 2 +- api/web_conn.go | 2 +- api/web_hub.go | 2 +- api/web_socket.go | 2 +- api/web_team_hub.go | 2 +- api/webhook.go | 2 +- config/config.json | 31 +++++++++++++++++++++++++++---- manualtesting/manual_testing.go | 2 +- manualtesting/test_autolink.go | 2 +- mattermost.go | 2 +- model/client.go | 2 +- store/sql_preference_store.go | 2 +- store/sql_session_store.go | 2 +- store/sql_store.go | 2 +- store/store.go | 2 +- utils/config.go | 6 ++++-- utils/mail.go | 2 +- web/web.go | 2 +- 31 files changed, 60 insertions(+), 35 deletions(-) diff --git a/api/admin.go b/api/admin.go index 8e0a03e4b4..885a95d959 100644 --- a/api/admin.go +++ b/api/admin.go @@ -9,7 +9,7 @@ import ( "os" "strings" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" diff --git a/api/api.go b/api/api.go index 6c7eda0a2f..a6bb229823 100644 --- a/api/api.go +++ b/api/api.go @@ -5,7 +5,7 @@ package api import ( "bytes" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" "html/template" diff --git a/api/channel.go b/api/channel.go index 674293e192..706baa004e 100644 --- a/api/channel.go +++ b/api/channel.go @@ -4,8 +4,8 @@ package api import ( - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "net/http" diff --git a/api/command.go b/api/command.go index db57f0baed..00293cf16f 100644 --- a/api/command.go +++ b/api/command.go @@ -11,7 +11,7 @@ import ( "strings" "time" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" diff --git a/api/context.go b/api/context.go index b39f03a7dc..561884c145 100644 --- a/api/context.go +++ b/api/context.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" diff --git a/api/file.go b/api/file.go index 67ebc14b7c..d023515af1 100644 --- a/api/file.go +++ b/api/file.go @@ -5,8 +5,8 @@ package api import ( "bytes" - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" "github.com/goamz/goamz/aws" "github.com/goamz/goamz/s3" diff --git a/api/import.go b/api/import.go index 81de789752..5c8f993488 100644 --- a/api/import.go +++ b/api/import.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) diff --git a/api/oauth.go b/api/oauth.go index 5753db8bd6..eb5e0e4968 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -4,8 +4,8 @@ package api import ( - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" diff --git a/api/post.go b/api/post.go index 958479427e..be1ecd96a5 100644 --- a/api/post.go +++ b/api/post.go @@ -4,8 +4,8 @@ package api import ( - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" diff --git a/api/preference.go b/api/preference.go index e9c74aafe7..f5c96f1dd6 100644 --- a/api/preference.go +++ b/api/preference.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "net/http" diff --git a/api/server.go b/api/server.go index 2bab62facf..33428009f6 100644 --- a/api/server.go +++ b/api/server.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/braintree/manners" "github.com/gorilla/mux" "github.com/mattermost/platform/store" diff --git a/api/slackimport.go b/api/slackimport.go index cab4c61847..e0a0ff0368 100644 --- a/api/slackimport.go +++ b/api/slackimport.go @@ -6,8 +6,8 @@ package api import ( "archive/zip" "bytes" - l4g "code.google.com/p/log4go" "encoding/json" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" "io" "mime/multipart" diff --git a/api/team.go b/api/team.go index fbcb301a9a..e2dd8807ed 100644 --- a/api/team.go +++ b/api/team.go @@ -5,8 +5,8 @@ package api import ( "bytes" - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" diff --git a/api/user.go b/api/user.go index d4c7fcaf59..6c0ce86d69 100644 --- a/api/user.go +++ b/api/user.go @@ -5,9 +5,9 @@ package api import ( "bytes" - l4g "code.google.com/p/log4go" b64 "encoding/base64" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" "github.com/golang/freetype" "github.com/gorilla/mux" diff --git a/api/web_conn.go b/api/web_conn.go index 50a003acea..2b0e290382 100644 --- a/api/web_conn.go +++ b/api/web_conn.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/websocket" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" diff --git a/api/web_hub.go b/api/web_hub.go index f804888249..4361d10354 100644 --- a/api/web_hub.go +++ b/api/web_hub.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) diff --git a/api/web_socket.go b/api/web_socket.go index 298e44b448..995e2a6776 100644 --- a/api/web_socket.go +++ b/api/web_socket.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/mattermost/platform/model" diff --git a/api/web_team_hub.go b/api/web_team_hub.go index 2c2386317a..bb9ed9526c 100644 --- a/api/web_team_hub.go +++ b/api/web_team_hub.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) diff --git a/api/webhook.go b/api/webhook.go index 34c3088790..a9a88b7b80 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -4,7 +4,7 @@ package api import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" diff --git a/config/config.json b/config/config.json index c43db1e50c..907b668283 100644 --- a/config/config.json +++ b/config/config.json @@ -12,10 +12,10 @@ "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, - "SessionLengthWebInDays" : 30, - "SessionLengthMobileInDays" : 30, - "SessionLengthSSOInDays" : 30, - "SessionCacheInMinutes" : 10 + "SessionLengthWebInDays": 30, + "SessionLengthMobileInDays": 30, + "SessionLengthSSOInDays": 30, + "SessionCacheInMinutes": 10 }, "TeamSettings": { "SiteName": "Mattermost", @@ -107,5 +107,28 @@ "AuthEndpoint": "", "TokenEndpoint": "", "UserApiEndpoint": "" + }, + "GoogleSettings": { + "Enable": false, + "Secret": "", + "Id": "", + "Scope": "", + "AuthEndpoint": "", + "TokenEndpoint": "", + "UserApiEndpoint": "" + }, + "LdapSettings": { + "Enable": false, + "LdapServer": null, + "LdapPort": 389, + "BaseDN": null, + "BindUsername": null, + "BindPassword": null, + "FirstNameAttribute": null, + "LastNameAttribute": null, + "EmailAttribute": null, + "UsernameAttribute": null, + "IdAttribute": null, + "QueryTimeout": 60 } } \ No newline at end of file diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index b3d01a5a69..befc835fb1 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -4,7 +4,7 @@ package manualtesting import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/api" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" diff --git a/manualtesting/test_autolink.go b/manualtesting/test_autolink.go index a13ec7a752..16d2d713a1 100644 --- a/manualtesting/test_autolink.go +++ b/manualtesting/test_autolink.go @@ -4,7 +4,7 @@ package manualtesting import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) diff --git a/mattermost.go b/mattermost.go index 3d8ab736f9..f86af76e36 100644 --- a/mattermost.go +++ b/mattermost.go @@ -17,7 +17,7 @@ import ( "syscall" "time" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/api" "github.com/mattermost/platform/manualtesting" "github.com/mattermost/platform/model" diff --git a/model/client.go b/model/client.go index 14746f8ae6..75b93c9710 100644 --- a/model/client.go +++ b/model/client.go @@ -5,8 +5,8 @@ package model import ( "bytes" - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "io/ioutil" "net/http" "net/url" diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go index 3077611503..6302d2f4f8 100644 --- a/store/sql_preference_store.go +++ b/store/sql_preference_store.go @@ -4,7 +4,7 @@ package store import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/go-gorp/gorp" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" diff --git a/store/sql_session_store.go b/store/sql_session_store.go index 86604b4fe7..6b0a314432 100644 --- a/store/sql_session_store.go +++ b/store/sql_session_store.go @@ -4,7 +4,7 @@ package store import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) diff --git a/store/sql_store.go b/store/sql_store.go index d17a3e8c3d..d0471fa1e7 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -4,7 +4,6 @@ package store import ( - l4g "code.google.com/p/log4go" "crypto/aes" "crypto/cipher" "crypto/hmac" @@ -16,6 +15,7 @@ import ( "encoding/json" "errors" "fmt" + l4g "github.com/alecthomas/log4go" "io" sqltrace "log" "math/rand" diff --git a/store/store.go b/store/store.go index 8e03c8ee72..179cfecd7b 100644 --- a/store/store.go +++ b/store/store.go @@ -4,7 +4,7 @@ package store import ( - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" "time" ) diff --git a/utils/config.go b/utils/config.go index 18bd152414..12d03b5de8 100644 --- a/utils/config.go +++ b/utils/config.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strconv" - l4g "code.google.com/p/log4go" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" ) @@ -77,7 +77,9 @@ func configureLog(s *model.LogSettings) { level = l4g.ERROR } - l4g.AddFilter("stdout", level, l4g.NewConsoleLogWriter()) + lw := l4g.NewConsoleLogWriter() + lw.SetFormat("[%D %T] [%L] %M") + l4g.AddFilter("stdout", level, lw) } if s.EnableFile { diff --git a/utils/mail.go b/utils/mail.go index 6625060de8..2f2c10b619 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -4,10 +4,10 @@ package utils import ( - l4g "code.google.com/p/log4go" "crypto/tls" "encoding/base64" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" "net" "net/mail" diff --git a/web/web.go b/web/web.go index bf1208adcb..5cc0e288d3 100644 --- a/web/web.go +++ b/web/web.go @@ -4,8 +4,8 @@ package web import ( - l4g "code.google.com/p/log4go" "fmt" + l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/api" "github.com/mattermost/platform/model" From 30bb530903352039c1b5055a756b7e246e2406b6 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 09:13:38 -0600 Subject: [PATCH 32/61] Upgrading logging package --- Godeps/Godeps.json | 5 +- .../src/code.google.com/p/log4go/.hgtags | 4 - .../code.google.com/p/log4go/log4go_test.go | 534 ------------------ .../src/code.google.com/p/log4go/termlog.go | 45 -- .../github.com/alecthomas/log4go/.gitignore | 2 + .../alecthomas}/log4go/LICENSE | 0 .../p => github.com/alecthomas}/log4go/README | 0 .../alecthomas}/log4go/config.go | 4 +- .../examples/ConsoleLogWriter_Manual.go | 1 + .../log4go/examples/FileLogWriter_Manual.go | 0 .../log4go/examples/SimpleNetLogServer.go | 0 .../log4go/examples/SocketLogWriter_Manual.go | 0 .../examples/XMLConfigurationExample.go | 0 .../alecthomas}/log4go/examples/example.xml | 0 .../alecthomas}/log4go/filelog.go | 53 +- .../alecthomas}/log4go/log4go.go | 32 +- .../alecthomas}/log4go/pattlog.go | 8 +- .../alecthomas}/log4go/socklog.go | 0 .../github.com/alecthomas/log4go/termlog.go | 49 ++ .../alecthomas}/log4go/wrapper.go | 10 +- 20 files changed, 122 insertions(+), 625 deletions(-) delete mode 100644 Godeps/_workspace/src/code.google.com/p/log4go/.hgtags delete mode 100644 Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go delete mode 100644 Godeps/_workspace/src/code.google.com/p/log4go/termlog.go create mode 100644 Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/LICENSE (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/README (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/config.go (99%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/ConsoleLogWriter_Manual.go (93%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/FileLogWriter_Manual.go (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/SimpleNetLogServer.go (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/SocketLogWriter_Manual.go (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/XMLConfigurationExample.go (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/examples/example.xml (100%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/filelog.go (83%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/log4go.go (95%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/pattlog.go (94%) rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/socklog.go (100%) create mode 100644 Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go rename Godeps/_workspace/src/{code.google.com/p => github.com/alecthomas}/log4go/wrapper.go (97%) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index bd2392f90e..d9c4a61bdd 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -3,9 +3,8 @@ "GoVersion": "go1.5.1", "Deps": [ { - "ImportPath": "code.google.com/p/log4go", - "Comment": "go.weekly.2012-02-22-1", - "Rev": "c3294304d93f48a37d3bed1d382882a9c2989f99" + "ImportPath": "github.com/alecthomas/log4go", + "Rev": "8e9057c3b25c409a34c0b9737cdc82cbcafeabce" }, { "ImportPath": "github.com/braintree/manners", diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags b/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags deleted file mode 100644 index 72a2eea2cc..0000000000 --- a/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags +++ /dev/null @@ -1,4 +0,0 @@ -4fbe6aadba231e838a449d340e43bdaab0bf85bd go.weekly.2012-02-07 -56168fd53249d639c25c74ced881fffb20d27be9 go.weekly.2012-02-22 -56168fd53249d639c25c74ced881fffb20d27be9 go.weekly.2012-02-22 -5c22fbd77d91f54d76cdbdee05318699754c44cc go.weekly.2012-02-22 diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go b/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go deleted file mode 100644 index 90c629977e..0000000000 --- a/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "os" - "runtime" - "testing" - "time" -) - -const testLogFile = "_logtest.log" - -var now time.Time = time.Unix(0, 1234567890123456789).In(time.UTC) - -func newLogRecord(lvl level, src string, msg string) *LogRecord { - return &LogRecord{ - Level: lvl, - Source: src, - Created: now, - Message: msg, - } -} - -func TestELog(t *testing.T) { - fmt.Printf("Testing %s\n", L4G_VERSION) - lr := newLogRecord(CRITICAL, "source", "message") - if lr.Level != CRITICAL { - t.Errorf("Incorrect level: %d should be %d", lr.Level, CRITICAL) - } - if lr.Source != "source" { - t.Errorf("Incorrect source: %s should be %s", lr.Source, "source") - } - if lr.Message != "message" { - t.Errorf("Incorrect message: %s should be %s", lr.Source, "message") - } -} - -var formatTests = []struct { - Test string - Record *LogRecord - Formats map[string]string -}{ - { - Test: "Standard formats", - Record: &LogRecord{ - Level: ERROR, - Source: "source", - Message: "message", - Created: now, - }, - Formats: map[string]string{ - // TODO(kevlar): How can I do this so it'll work outside of PST? - FORMAT_DEFAULT: "[2009/02/13 23:31:30 UTC] [EROR] (source) message\n", - FORMAT_SHORT: "[23:31 02/13/09] [EROR] message\n", - FORMAT_ABBREV: "[EROR] message\n", - }, - }, -} - -func TestFormatLogRecord(t *testing.T) { - for _, test := range formatTests { - name := test.Test - for fmt, want := range test.Formats { - if got := FormatLogRecord(fmt, test.Record); got != want { - t.Errorf("%s - %s:", name, fmt) - t.Errorf(" got %q", got) - t.Errorf(" want %q", want) - } - } - } -} - -var logRecordWriteTests = []struct { - Test string - Record *LogRecord - Console string -}{ - { - Test: "Normal message", - Record: &LogRecord{ - Level: CRITICAL, - Source: "source", - Message: "message", - Created: now, - }, - Console: "[02/13/09 23:31:30] [CRIT] message\n", - }, -} - -func TestConsoleLogWriter(t *testing.T) { - console := make(ConsoleLogWriter) - - r, w := io.Pipe() - go console.run(w) - defer console.Close() - - buf := make([]byte, 1024) - - for _, test := range logRecordWriteTests { - name := test.Test - - console.LogWrite(test.Record) - n, _ := r.Read(buf) - - if got, want := string(buf[:n]), test.Console; got != want { - t.Errorf("%s: got %q", name, got) - t.Errorf("%s: want %q", name, want) - } - } -} - -func TestFileLogWriter(t *testing.T) { - defer func(buflen int) { - LogBufferLength = buflen - }(LogBufferLength) - LogBufferLength = 0 - - w := NewFileLogWriter(testLogFile, false) - if w == nil { - t.Fatalf("Invalid return: w should not be nil") - } - defer os.Remove(testLogFile) - - w.LogWrite(newLogRecord(CRITICAL, "source", "message")) - w.Close() - runtime.Gosched() - - if contents, err := ioutil.ReadFile(testLogFile); err != nil { - t.Errorf("read(%q): %s", testLogFile, err) - } else if len(contents) != 50 { - t.Errorf("malformed filelog: %q (%d bytes)", string(contents), len(contents)) - } -} - -func TestXMLLogWriter(t *testing.T) { - defer func(buflen int) { - LogBufferLength = buflen - }(LogBufferLength) - LogBufferLength = 0 - - w := NewXMLLogWriter(testLogFile, false) - if w == nil { - t.Fatalf("Invalid return: w should not be nil") - } - defer os.Remove(testLogFile) - - w.LogWrite(newLogRecord(CRITICAL, "source", "message")) - w.Close() - runtime.Gosched() - - if contents, err := ioutil.ReadFile(testLogFile); err != nil { - t.Errorf("read(%q): %s", testLogFile, err) - } else if len(contents) != 185 { - t.Errorf("malformed xmllog: %q (%d bytes)", string(contents), len(contents)) - } -} - -func TestLogger(t *testing.T) { - sl := NewDefaultLogger(WARNING) - if sl == nil { - t.Fatalf("NewDefaultLogger should never return nil") - } - if lw, exist := sl["stdout"]; lw == nil || exist != true { - t.Fatalf("NewDefaultLogger produced invalid logger (DNE or nil)") - } - if sl["stdout"].Level != WARNING { - t.Fatalf("NewDefaultLogger produced invalid logger (incorrect level)") - } - if len(sl) != 1 { - t.Fatalf("NewDefaultLogger produced invalid logger (incorrect map count)") - } - - //func (l *Logger) AddFilter(name string, level int, writer LogWriter) {} - l := make(Logger) - l.AddFilter("stdout", DEBUG, NewConsoleLogWriter()) - if lw, exist := l["stdout"]; lw == nil || exist != true { - t.Fatalf("AddFilter produced invalid logger (DNE or nil)") - } - if l["stdout"].Level != DEBUG { - t.Fatalf("AddFilter produced invalid logger (incorrect level)") - } - if len(l) != 1 { - t.Fatalf("AddFilter produced invalid logger (incorrect map count)") - } - - //func (l *Logger) Warn(format string, args ...interface{}) error {} - if err := l.Warn("%s %d %#v", "Warning:", 1, []int{}); err.Error() != "Warning: 1 []int{}" { - t.Errorf("Warn returned invalid error: %s", err) - } - - //func (l *Logger) Error(format string, args ...interface{}) error {} - if err := l.Error("%s %d %#v", "Error:", 10, []string{}); err.Error() != "Error: 10 []string{}" { - t.Errorf("Error returned invalid error: %s", err) - } - - //func (l *Logger) Critical(format string, args ...interface{}) error {} - if err := l.Critical("%s %d %#v", "Critical:", 100, []int64{}); err.Error() != "Critical: 100 []int64{}" { - t.Errorf("Critical returned invalid error: %s", err) - } - - // Already tested or basically untestable - //func (l *Logger) Log(level int, source, message string) {} - //func (l *Logger) Logf(level int, format string, args ...interface{}) {} - //func (l *Logger) intLogf(level int, format string, args ...interface{}) string {} - //func (l *Logger) Finest(format string, args ...interface{}) {} - //func (l *Logger) Fine(format string, args ...interface{}) {} - //func (l *Logger) Debug(format string, args ...interface{}) {} - //func (l *Logger) Trace(format string, args ...interface{}) {} - //func (l *Logger) Info(format string, args ...interface{}) {} -} - -func TestLogOutput(t *testing.T) { - const ( - expected = "fdf3e51e444da56b4cb400f30bc47424" - ) - - // Unbuffered output - defer func(buflen int) { - LogBufferLength = buflen - }(LogBufferLength) - LogBufferLength = 0 - - l := make(Logger) - - // Delete and open the output log without a timestamp (for a constant md5sum) - l.AddFilter("file", FINEST, NewFileLogWriter(testLogFile, false).SetFormat("[%L] %M")) - defer os.Remove(testLogFile) - - // Send some log messages - l.Log(CRITICAL, "testsrc1", fmt.Sprintf("This message is level %d", int(CRITICAL))) - l.Logf(ERROR, "This message is level %v", ERROR) - l.Logf(WARNING, "This message is level %s", WARNING) - l.Logc(INFO, func() string { return "This message is level INFO" }) - l.Trace("This message is level %d", int(TRACE)) - l.Debug("This message is level %s", DEBUG) - l.Fine(func() string { return fmt.Sprintf("This message is level %v", FINE) }) - l.Finest("This message is level %v", FINEST) - l.Finest(FINEST, "is also this message's level") - - l.Close() - - contents, err := ioutil.ReadFile(testLogFile) - if err != nil { - t.Fatalf("Could not read output log: %s", err) - } - - sum := md5.New() - sum.Write(contents) - if sumstr := hex.EncodeToString(sum.Sum(nil)); sumstr != expected { - t.Errorf("--- Log Contents:\n%s---", string(contents)) - t.Fatalf("Checksum does not match: %s (expecting %s)", sumstr, expected) - } -} - -func TestCountMallocs(t *testing.T) { - const N = 1 - var m runtime.MemStats - getMallocs := func() uint64 { - runtime.ReadMemStats(&m) - return m.Mallocs - } - - // Console logger - sl := NewDefaultLogger(INFO) - mallocs := 0 - getMallocs() - for i := 0; i < N; i++ { - sl.Log(WARNING, "here", "This is a WARNING message") - } - mallocs += getMallocs() - fmt.Printf("mallocs per sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) - - // Console logger formatted - mallocs = 0 - getMallocs() - for i := 0; i < N; i++ { - sl.Logf(WARNING, "%s is a log message with level %d", "This", WARNING) - } - mallocs += getMallocs() - fmt.Printf("mallocs per sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) - - // Console logger (not logged) - sl = NewDefaultLogger(INFO) - mallocs = 0 - getMallocs() - for i := 0; i < N; i++ { - sl.Log(DEBUG, "here", "This is a DEBUG log message") - } - mallocs += getMallocs() - fmt.Printf("mallocs per unlogged sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) - - // Console logger formatted (not logged) - mallocs = 0 - getMallocs() - for i := 0; i < N; i++ { - sl.Logf(DEBUG, "%s is a log message with level %d", "This", DEBUG) - } - mallocs += getMallocs() - fmt.Printf("mallocs per unlogged sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) -} - -func TestXMLConfig(t *testing.T) { - const ( - configfile = "example.xml" - ) - - fd, err := os.Create(configfile) - if err != nil { - t.Fatalf("Could not open %s for writing: %s", configfile, err) - } - - fmt.Fprintln(fd, "") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " stdout") - fmt.Fprintln(fd, " console") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " DEBUG") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " file") - fmt.Fprintln(fd, " file") - fmt.Fprintln(fd, " FINEST") - fmt.Fprintln(fd, " test.log") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " [%D %T] [%L] (%S) %M") - fmt.Fprintln(fd, " false ") - fmt.Fprintln(fd, " 0M ") - fmt.Fprintln(fd, " 0K ") - fmt.Fprintln(fd, " true ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " xmllog") - fmt.Fprintln(fd, " xml") - fmt.Fprintln(fd, " TRACE") - fmt.Fprintln(fd, " trace.xml") - fmt.Fprintln(fd, " true ") - fmt.Fprintln(fd, " 100M ") - fmt.Fprintln(fd, " 6K ") - fmt.Fprintln(fd, " false ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, " donotopen") - fmt.Fprintln(fd, " socket") - fmt.Fprintln(fd, " FINEST") - fmt.Fprintln(fd, " 192.168.1.255:12124 ") - fmt.Fprintln(fd, " udp ") - fmt.Fprintln(fd, " ") - fmt.Fprintln(fd, "") - fd.Close() - - log := make(Logger) - log.LoadConfiguration(configfile) - defer os.Remove("trace.xml") - defer os.Remove("test.log") - defer log.Close() - - // Make sure we got all loggers - if len(log) != 3 { - t.Fatalf("XMLConfig: Expected 3 filters, found %d", len(log)) - } - - // Make sure they're the right keys - if _, ok := log["stdout"]; !ok { - t.Errorf("XMLConfig: Expected stdout logger") - } - if _, ok := log["file"]; !ok { - t.Fatalf("XMLConfig: Expected file logger") - } - if _, ok := log["xmllog"]; !ok { - t.Fatalf("XMLConfig: Expected xmllog logger") - } - - // Make sure they're the right type - if _, ok := log["stdout"].LogWriter.(ConsoleLogWriter); !ok { - t.Fatalf("XMLConfig: Expected stdout to be ConsoleLogWriter, found %T", log["stdout"].LogWriter) - } - if _, ok := log["file"].LogWriter.(*FileLogWriter); !ok { - t.Fatalf("XMLConfig: Expected file to be *FileLogWriter, found %T", log["file"].LogWriter) - } - if _, ok := log["xmllog"].LogWriter.(*FileLogWriter); !ok { - t.Fatalf("XMLConfig: Expected xmllog to be *FileLogWriter, found %T", log["xmllog"].LogWriter) - } - - // Make sure levels are set - if lvl := log["stdout"].Level; lvl != DEBUG { - t.Errorf("XMLConfig: Expected stdout to be set to level %d, found %d", DEBUG, lvl) - } - if lvl := log["file"].Level; lvl != FINEST { - t.Errorf("XMLConfig: Expected file to be set to level %d, found %d", FINEST, lvl) - } - if lvl := log["xmllog"].Level; lvl != TRACE { - t.Errorf("XMLConfig: Expected xmllog to be set to level %d, found %d", TRACE, lvl) - } - - // Make sure the w is open and points to the right file - if fname := log["file"].LogWriter.(*FileLogWriter).file.Name(); fname != "test.log" { - t.Errorf("XMLConfig: Expected file to have opened %s, found %s", "test.log", fname) - } - - // Make sure the XLW is open and points to the right file - if fname := log["xmllog"].LogWriter.(*FileLogWriter).file.Name(); fname != "trace.xml" { - t.Errorf("XMLConfig: Expected xmllog to have opened %s, found %s", "trace.xml", fname) - } - - // Move XML log file - os.Rename(configfile, "examples/"+configfile) // Keep this so that an example with the documentation is available -} - -func BenchmarkFormatLogRecord(b *testing.B) { - const updateEvery = 1 - rec := &LogRecord{ - Level: CRITICAL, - Created: now, - Source: "source", - Message: "message", - } - for i := 0; i < b.N; i++ { - rec.Created = rec.Created.Add(1 * time.Second / updateEvery) - if i%2 == 0 { - FormatLogRecord(FORMAT_DEFAULT, rec) - } else { - FormatLogRecord(FORMAT_SHORT, rec) - } - } -} - -func BenchmarkConsoleLog(b *testing.B) { - /* This doesn't seem to work on OS X - sink, err := os.Open(os.DevNull) - if err != nil { - panic(err) - } - if err := syscall.Dup2(int(sink.Fd()), syscall.Stdout); err != nil { - panic(err) - } - */ - - stdout = ioutil.Discard - sl := NewDefaultLogger(INFO) - for i := 0; i < b.N; i++ { - sl.Log(WARNING, "here", "This is a log message") - } -} - -func BenchmarkConsoleNotLogged(b *testing.B) { - sl := NewDefaultLogger(INFO) - for i := 0; i < b.N; i++ { - sl.Log(DEBUG, "here", "This is a log message") - } -} - -func BenchmarkConsoleUtilLog(b *testing.B) { - sl := NewDefaultLogger(INFO) - for i := 0; i < b.N; i++ { - sl.Info("%s is a log message", "This") - } -} - -func BenchmarkConsoleUtilNotLog(b *testing.B) { - sl := NewDefaultLogger(INFO) - for i := 0; i < b.N; i++ { - sl.Debug("%s is a log message", "This") - } -} - -func BenchmarkFileLog(b *testing.B) { - sl := make(Logger) - b.StopTimer() - sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) - b.StartTimer() - for i := 0; i < b.N; i++ { - sl.Log(WARNING, "here", "This is a log message") - } - b.StopTimer() - os.Remove("benchlog.log") -} - -func BenchmarkFileNotLogged(b *testing.B) { - sl := make(Logger) - b.StopTimer() - sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) - b.StartTimer() - for i := 0; i < b.N; i++ { - sl.Log(DEBUG, "here", "This is a log message") - } - b.StopTimer() - os.Remove("benchlog.log") -} - -func BenchmarkFileUtilLog(b *testing.B) { - sl := make(Logger) - b.StopTimer() - sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) - b.StartTimer() - for i := 0; i < b.N; i++ { - sl.Info("%s is a log message", "This") - } - b.StopTimer() - os.Remove("benchlog.log") -} - -func BenchmarkFileUtilNotLog(b *testing.B) { - sl := make(Logger) - b.StopTimer() - sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) - b.StartTimer() - for i := 0; i < b.N; i++ { - sl.Debug("%s is a log message", "This") - } - b.StopTimer() - os.Remove("benchlog.log") -} - -// Benchmark results (darwin amd64 6g) -//elog.BenchmarkConsoleLog 100000 22819 ns/op -//elog.BenchmarkConsoleNotLogged 2000000 879 ns/op -//elog.BenchmarkConsoleUtilLog 50000 34380 ns/op -//elog.BenchmarkConsoleUtilNotLog 1000000 1339 ns/op -//elog.BenchmarkFileLog 100000 26497 ns/op -//elog.BenchmarkFileNotLogged 2000000 821 ns/op -//elog.BenchmarkFileUtilLog 50000 33945 ns/op -//elog.BenchmarkFileUtilNotLog 1000000 1258 ns/op diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go b/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go deleted file mode 100644 index 1ed2e4e0de..0000000000 --- a/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "io" - "os" - "fmt" -) - -var stdout io.Writer = os.Stdout - -// This is the standard writer that prints to standard output. -type ConsoleLogWriter chan *LogRecord - -// This creates a new ConsoleLogWriter -func NewConsoleLogWriter() ConsoleLogWriter { - records := make(ConsoleLogWriter, LogBufferLength) - go records.run(stdout) - return records -} - -func (w ConsoleLogWriter) run(out io.Writer) { - var timestr string - var timestrAt int64 - - for rec := range w { - if at := rec.Created.UnixNano() / 1e9; at != timestrAt { - timestr, timestrAt = rec.Created.Format("01/02/06 15:04:05"), at - } - fmt.Fprint(out, "[", timestr, "] [", levelStrings[rec.Level], "] ", rec.Message, "\n") - } -} - -// This is the ConsoleLogWriter's output method. This will block if the output -// buffer is full. -func (w ConsoleLogWriter) LogWrite(rec *LogRecord) { - w <- rec -} - -// Close stops the logger from sending messages to standard output. Attempts to -// send log messages to this logger after a Close have undefined behavior. -func (w ConsoleLogWriter) Close() { - close(w) -} diff --git a/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore b/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore new file mode 100644 index 0000000000..f6207cd8a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore @@ -0,0 +1,2 @@ +*.sw[op] +.DS_Store diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/LICENSE b/Godeps/_workspace/src/github.com/alecthomas/log4go/LICENSE similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/LICENSE rename to Godeps/_workspace/src/github.com/alecthomas/log4go/LICENSE diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/README b/Godeps/_workspace/src/github.com/alecthomas/log4go/README similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/README rename to Godeps/_workspace/src/github.com/alecthomas/log4go/README diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/config.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/config.go similarity index 99% rename from Godeps/_workspace/src/code.google.com/p/log4go/config.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/config.go index f048b69f56..577c3eb2f8 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/config.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/config.go @@ -53,7 +53,7 @@ func (log Logger) LoadConfiguration(filename string) { for _, xmlfilt := range xc.Filter { var filt LogWriter - var lvl level + var lvl Level bad, good, enabled := false, true, false // Check required children @@ -131,7 +131,7 @@ func (log Logger) LoadConfiguration(filename string) { } } -func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (ConsoleLogWriter, bool) { +func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) { // Parse properties for _, prop := range props { switch prop.Name { diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go similarity index 93% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go index 394ca83806..698dd332d8 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go @@ -8,6 +8,7 @@ import l4g "code.google.com/p/log4go" func main() { log := l4g.NewLogger() + defer log.Close() log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter()) log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) } diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/FileLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/FileLogWriter_Manual.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SimpleNetLogServer.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/SimpleNetLogServer.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SocketLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/SocketLogWriter_Manual.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/XMLConfigurationExample.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/XMLConfigurationExample.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/example.xml b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/example.xml similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/examples/example.xml rename to Godeps/_workspace/src/github.com/alecthomas/log4go/examples/example.xml diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/filelog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go similarity index 83% rename from Godeps/_workspace/src/code.google.com/p/log4go/filelog.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go index 9cbd815d99..ee0ab0c046 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/filelog.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go @@ -3,8 +3,8 @@ package log4go import ( - "os" "fmt" + "os" "time" ) @@ -36,7 +36,8 @@ type FileLogWriter struct { daily_opendate int // Keep old logfiles (.001, .002, etc) - rotate bool + rotate bool + maxbackup int } // This is the FileLogWriter's output method @@ -46,6 +47,7 @@ func (w *FileLogWriter) LogWrite(rec *LogRecord) { func (w *FileLogWriter) Close() { close(w.rec) + w.file.Sync() } // NewFileLogWriter creates a new LogWriter which writes to the given file and @@ -59,11 +61,12 @@ func (w *FileLogWriter) Close() { // [%D %T] [%L] (%S) %M func NewFileLogWriter(fname string, rotate bool) *FileLogWriter { w := &FileLogWriter{ - rec: make(chan *LogRecord, LogBufferLength), - rot: make(chan bool), - filename: fname, - format: "[%D %T] [%L] (%S) %M", - rotate: rotate, + rec: make(chan *LogRecord, LogBufferLength), + rot: make(chan bool), + filename: fname, + format: "[%D %T] [%L] (%S) %M", + rotate: rotate, + maxbackup: 999, } // open the file for the first time @@ -138,15 +141,30 @@ func (w *FileLogWriter) intRotate() error { // Find the next available number num := 1 fname := "" - for ; err == nil && num <= 999; num++ { - fname = w.filename + fmt.Sprintf(".%03d", num) - _, err = os.Lstat(fname) - } - // return error if the last file checked still existed - if err == nil { - return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename) + if w.daily && time.Now().Day() != w.daily_opendate { + yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") + + for ; err == nil && num <= 999; num++ { + fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num) + _, err = os.Lstat(fname) + } + // return error if the last file checked still existed + if err == nil { + return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename) + } + } else { + num = w.maxbackup - 1 + for ; num >= 1; num-- { + fname = w.filename + fmt.Sprintf(".%d", num) + nfname := w.filename + fmt.Sprintf(".%d", num+1) + _, err = os.Lstat(fname) + if err == nil { + os.Rename(fname, nfname) + } + } } + w.file.Close() // Rename the file to its newfound home err = os.Rename(w.filename, fname) if err != nil { @@ -217,6 +235,13 @@ func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter { return w } +// Set max backup files. Must be called before the first log message +// is written. +func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter { + w.maxbackup = maxbackup + return w +} + // SetRotate changes whether or not the old logs are kept. (chainable) Must be // called before the first log message is written. If rotate is false, the // files are overwritten; otherwise, they are rotated to another file before the diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/log4go.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go similarity index 95% rename from Godeps/_workspace/src/code.google.com/p/log4go/log4go.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go index ab4e857f51..822e890ccb 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/log4go.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go @@ -47,11 +47,11 @@ package log4go import ( "errors" - "os" "fmt" - "time" - "strings" + "os" "runtime" + "strings" + "time" ) // Version information @@ -65,10 +65,10 @@ const ( /****** Constants ******/ // These are the integer logging levels used by the logger -type level int +type Level int const ( - FINEST level = iota + FINEST Level = iota FINE DEBUG TRACE @@ -83,7 +83,7 @@ var ( levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} ) -func (l level) String() string { +func (l Level) String() string { if l < 0 || int(l) > len(levelStrings) { return "UNKNOWN" } @@ -101,7 +101,7 @@ var ( // A LogRecord contains all of the pertinent information for each message type LogRecord struct { - Level level // The log level + Level Level // The log level Created time.Time // The time at which the log message was created (nanoseconds) Source string // The message source Message string // The log message @@ -124,7 +124,7 @@ type LogWriter interface { // A Filter represents the log level below which no log records are written to // the associated LogWriter. type Filter struct { - Level level + Level Level LogWriter } @@ -144,7 +144,7 @@ func NewLogger() Logger { // or above lvl to standard output. // // DEPRECATED: use NewDefaultLogger instead. -func NewConsoleLogger(lvl level) Logger { +func NewConsoleLogger(lvl Level) Logger { os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") return Logger{ "stdout": &Filter{lvl, NewConsoleLogWriter()}, @@ -153,7 +153,7 @@ func NewConsoleLogger(lvl level) Logger { // Create a new logger with a "stdout" filter configured to send log messages at // or above lvl to standard output. -func NewDefaultLogger(lvl level) Logger { +func NewDefaultLogger(lvl Level) Logger { return Logger{ "stdout": &Filter{lvl, NewConsoleLogWriter()}, } @@ -174,14 +174,14 @@ func (log Logger) Close() { // Add a new LogWriter to the Logger which will only log messages at lvl or // higher. This function should not be called from multiple goroutines. // Returns the logger for chaining. -func (log Logger) AddFilter(name string, lvl level, writer LogWriter) Logger { +func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { log[name] = &Filter{lvl, writer} return log } /******* Logging *******/ // Send a formatted log message internally -func (log Logger) intLogf(lvl level, format string, args ...interface{}) { +func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { skip := true // Determine if any logging will be done @@ -225,7 +225,7 @@ func (log Logger) intLogf(lvl level, format string, args ...interface{}) { } // Send a closure log message internally -func (log Logger) intLogc(lvl level, closure func() string) { +func (log Logger) intLogc(lvl Level, closure func() string) { skip := true // Determine if any logging will be done @@ -264,7 +264,7 @@ func (log Logger) intLogc(lvl level, closure func() string) { } // Send a log message with manual level, source, and message. -func (log Logger) Log(lvl level, source, message string) { +func (log Logger) Log(lvl Level, source, message string) { skip := true // Determine if any logging will be done @@ -297,13 +297,13 @@ func (log Logger) Log(lvl level, source, message string) { // Logf logs a formatted log message at the given log level, using the caller as // its source. -func (log Logger) Logf(lvl level, format string, args ...interface{}) { +func (log Logger) Logf(lvl Level, format string, args ...interface{}) { log.intLogf(lvl, format, args...) } // Logc logs a string returned by the closure at the given log level, using the caller as // its source. If no log message would be written, the closure is never called. -func (log Logger) Logc(lvl level, closure func() string) { +func (log Logger) Logc(lvl Level, closure func() string) { log.intLogc(lvl, closure) } diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go similarity index 94% rename from Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go index 8224302b36..82b4e36b1f 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go @@ -3,9 +3,10 @@ package log4go import ( - "fmt" "bytes" + "fmt" "io" + "strings" ) const ( @@ -51,7 +52,7 @@ func FormatLogRecord(format string, rec *LogRecord) string { updated := &formatCacheType{ LastUpdateSeconds: secs, shortTime: fmt.Sprintf("%02d:%02d", hour, minute), - shortDate: fmt.Sprintf("%02d/%02d/%02d", month, day, year%100), + shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100), longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone), longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day), } @@ -78,6 +79,9 @@ func FormatLogRecord(format string, rec *LogRecord) string { out.WriteString(levelStrings[rec.Level]) case 'S': out.WriteString(rec.Source) + case 's': + slice := strings.Split(rec.Source, "/") + out.WriteString(slice[len(slice)-1]) case 'M': out.WriteString(rec.Message) } diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/socklog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/socklog.go similarity index 100% rename from Godeps/_workspace/src/code.google.com/p/log4go/socklog.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/socklog.go diff --git a/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go new file mode 100644 index 0000000000..8a941e2698 --- /dev/null +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go @@ -0,0 +1,49 @@ +// Copyright (C) 2010, Kyle Lemons . All rights reserved. + +package log4go + +import ( + "fmt" + "io" + "os" + "time" +) + +var stdout io.Writer = os.Stdout + +// This is the standard writer that prints to standard output. +type ConsoleLogWriter struct { + format string + w chan *LogRecord +} + +// This creates a new ConsoleLogWriter +func NewConsoleLogWriter() *ConsoleLogWriter { + consoleWriter := &ConsoleLogWriter{ + format: "[%T %D] [%L] (%S) %M", + w: make(chan *LogRecord, LogBufferLength), + } + go consoleWriter.run(stdout) + return consoleWriter +} +func (c *ConsoleLogWriter) SetFormat(format string) { + c.format = format +} +func (c *ConsoleLogWriter) run(out io.Writer) { + for rec := range c.w { + fmt.Fprint(out, FormatLogRecord(c.format, rec)) + } +} + +// This is the ConsoleLogWriter's output method. This will block if the output +// buffer is full. +func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) { + c.w <- rec +} + +// Close stops the logger from sending messages to standard output. Attempts to +// send log messages to this logger after a Close have undefined behavior. +func (c *ConsoleLogWriter) Close() { + close(c.w) + time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete +} diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go similarity index 97% rename from Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go rename to Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go index 10ecd88e6d..2ae222b0c4 100644 --- a/Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go +++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go @@ -4,8 +4,8 @@ package log4go import ( "errors" - "os" "fmt" + "os" "strings" ) @@ -23,7 +23,7 @@ func LoadConfiguration(filename string) { } // Wrapper for (*Logger).AddFilter -func AddFilter(name string, lvl level, writer LogWriter) { +func AddFilter(name string, lvl Level, writer LogWriter) { Global.AddFilter(name, lvl, writer) } @@ -88,19 +88,19 @@ func Stdoutf(format string, args ...interface{}) { // Send a log message manually // Wrapper for (*Logger).Log -func Log(lvl level, source, message string) { +func Log(lvl Level, source, message string) { Global.Log(lvl, source, message) } // Send a formatted log message easily // Wrapper for (*Logger).Logf -func Logf(lvl level, format string, args ...interface{}) { +func Logf(lvl Level, format string, args ...interface{}) { Global.intLogf(lvl, format, args...) } // Send a closure log message // Wrapper for (*Logger).Logc -func Logc(lvl level, closure func() string) { +func Logc(lvl Level, closure func() string) { Global.intLogc(lvl, closure) } From e9c2fd7cbb0fd35531096af89345d400125809f0 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Mon, 11 Jan 2016 20:32:31 +0500 Subject: [PATCH 33/61] Adding away icon and changing online and offline icons --- web/react/components/sidebar.jsx | 2 +- .../components/user_settings/import_theme_modal.jsx | 1 + web/react/utils/constants.jsx | 13 +++++++++++-- web/react/utils/utils.jsx | 6 +++++- web/sass-files/sass/partials/_sidebar--left.scss | 4 ++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 18c360cb8b..eaeb7bb91a 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -372,7 +372,7 @@ export default class Sidebar extends React.Component { if (channel.status === 'online') { statusIcon = Constants.ONLINE_ICON_SVG; } else if (channel.status === 'away') { - statusIcon = Constants.ONLINE_ICON_SVG; + statusIcon = Constants.AWAY_ICON_SVG; } else { statusIcon = Constants.OFFLINE_ICON_SVG; } diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 3df9dfedfb..45b05f19b7 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -55,6 +55,7 @@ export default class ImportThemeModal extends React.Component { theme.sidebarHeaderBg = colors[1]; theme.sidebarHeaderTextColor = colors[5]; theme.onlineIndicator = colors[6]; + theme.awayIndicator = '#E0B333'; theme.mentionBj = colors[7]; theme.mentionColor = '#ffffff'; theme.centerChannelBg = '#ffffff'; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 0298ce5332..d6ccb9eddb 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -163,8 +163,9 @@ export default { OPEN_TEAM: 'O', MAX_POST_LEN: 4000, EMOJI_SIZE: 16, - ONLINE_ICON_SVG: "", - OFFLINE_ICON_SVG: "", + ONLINE_ICON_SVG: " ", + AWAY_ICON_SVG: " ", + OFFLINE_ICON_SVG: " ", MENU_ICON: " ", COMMENT_ICON: " ", UPDATE_TYPING_MS: 5000, @@ -180,6 +181,7 @@ export default { sidebarHeaderBg: '#2f81b7', sidebarHeaderTextColor: '#FFFFFF', onlineIndicator: '#7DBE00', + awayIndicator: '#DCBD4E', mentionBj: '#136197', mentionColor: '#bfcde8', centerChannelBg: '#f2f4f8', @@ -203,6 +205,7 @@ export default { sidebarHeaderBg: '#2389d7', sidebarHeaderTextColor: '#ffffff', onlineIndicator: '#7DBE00', + awayIndicator: '#DCBD4E', mentionBj: '#2389d7', mentionColor: '#ffffff', centerChannelBg: '#ffffff', @@ -226,6 +229,7 @@ export default { sidebarHeaderBg: '#1B2C3E', sidebarHeaderTextColor: '#FFFFFF', onlineIndicator: '#55C5B2', + awayIndicator: '#A9A14C', mentionBj: '#B74A4A', mentionColor: '#FFFFFF', centerChannelBg: '#2F3E4E', @@ -249,6 +253,7 @@ export default { sidebarHeaderBg: '#1f1f1f', sidebarHeaderTextColor: '#FFFFFF', onlineIndicator: '#0177e7', + awayIndicator: '#A9A14C', mentionBj: '#0177e7', mentionColor: '#FFFFFF', centerChannelBg: '#1F1F1F', @@ -299,6 +304,10 @@ export default { id: 'onlineIndicator', uiName: 'Online Indicator' }, + { + id: 'awayIndicator', + uiName: 'Away Indicator' + }, { id: 'mentionBj', uiName: 'Mention Jewel BG' diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 95eca7c3a1..c0da0e2c67 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -557,7 +557,7 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1); changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); - changeCss('.sidebar--left .status path', 'fill:' + theme.sidebarText, 1); + changeCss('.sidebar--left .status .offline--icon, .sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText, 1); changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2); } @@ -602,6 +602,10 @@ export function applyTheme(theme) { changeCss('.sidebar--left .status .online--icon', 'fill:' + theme.onlineIndicator, 1); } + if (theme.awayIndicator) { + changeCss('.sidebar--left .status .away--icon', 'fill:' + theme.awayIndicator, 1); + } + if (theme.mentionBj) { changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj, 1); changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj, 1); diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss index e99e212573..6f969ed47f 100644 --- a/web/sass-files/sass/partials/_sidebar--left.scss +++ b/web/sass-files/sass/partials/_sidebar--left.scss @@ -42,9 +42,9 @@ margin-right: 6px; width: 12px; display: inline-block; - i, path { + i, path, ellipse { @include opacity(0.5); - &.online--icon { + &.online--icon, &.away--icon { @include opacity(1); } } From 5de20f013323d59bb81f043c47c177157c4f68d3 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Sat, 9 Jan 2016 12:42:34 -0500 Subject: [PATCH 34/61] Restricted file uploads on iOS Chrome and the iOS app to work around iOS bugs --- web/react/components/file_upload.jsx | 29 ++++++++++++++++++++-------- web/react/utils/utils.jsx | 15 ++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index a0c930ffb7..6337afabc4 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -4,7 +4,7 @@ import * as client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; import ChannelStore from '../stores/channel_store.jsx'; -import * as utils from '../utils/utils.jsx'; +import * as Utils from '../utils/utils.jsx'; export default class FileUpload extends React.Component { constructor(props) { @@ -52,7 +52,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - let clientId = utils.generateId(); + let clientId = Utils.generateId(); // prepare data to be uploaded var formData = new FormData(); @@ -121,14 +121,14 @@ export default class FileUpload extends React.Component { enter(dragsterEvent, e) { var files = e.originalEvent.dataTransfer; - if (utils.isFileTransfer(files)) { + if (Utils.isFileTransfer(files)) { $('.center-file-overlay').removeClass('hidden'); } }, leave(dragsterEvent, e) { var files = e.originalEvent.dataTransfer; - if (utils.isFileTransfer(files)) { + if (Utils.isFileTransfer(files)) { $('.center-file-overlay').addClass('hidden'); } }, @@ -142,14 +142,14 @@ export default class FileUpload extends React.Component { enter(dragsterEvent, e) { var files = e.originalEvent.dataTransfer; - if (utils.isFileTransfer(files)) { + if (Utils.isFileTransfer(files)) { $('.right-file-overlay').removeClass('hidden'); } }, leave(dragsterEvent, e) { var files = e.originalEvent.dataTransfer; - if (utils.isFileTransfer(files)) { + if (Utils.isFileTransfer(files)) { $('.right-file-overlay').addClass('hidden'); } }, @@ -205,7 +205,7 @@ export default class FileUpload extends React.Component { var channelId = self.props.channelId || ChannelStore.getCurrentId(); // generate a unique id that can be used by other components to refer back to this file upload - var clientId = utils.generateId(); + var clientId = Utils.generateId(); var formData = new FormData(); formData.append('channel_id', channelId); @@ -268,6 +268,18 @@ export default class FileUpload extends React.Component { } render() { + let multiple = true; + if (Utils.isMobileApp()) { + // iOS WebViews don't upload videos properly in multiple mode + multiple = false; + } + + let accept = ''; + if (Utils.isIosChrome()) { + // iOS Chrome can't upload videos at all + accept = 'image/*'; + } + return ( ); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 95eca7c3a1..2ddd0e5e36 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -74,6 +74,21 @@ export function isSafari() { return false; } +export function isIosChrome() { + // https://developer.chrome.com/multidevice/user-agent + return navigator.userAgent.indexOf('CriOS') !== -1; +} + +export function isMobileApp() { + const userAgent = navigator.userAgent; + + // the mobile app has different user agents for the native api calls and the shim, so handle them both + const isApi = userAgent.indexOf('Mattermost') !== -1; + const isShim = userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') === -1 && userAgent.indexOf('Chrome') === -1; + + return isApi || isShim; +} + export function isInRole(roles, inRole) { var parts = roles.split(' '); for (var i = 0; i < parts.length; i++) { From a41c807c700979c4dfb0cdda179e889f022dd80b Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 09:46:30 -0600 Subject: [PATCH 35/61] modifying config --- config/config.json | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/config/config.json b/config/config.json index 907b668283..076f795cc3 100644 --- a/config/config.json +++ b/config/config.json @@ -107,28 +107,5 @@ "AuthEndpoint": "", "TokenEndpoint": "", "UserApiEndpoint": "" - }, - "GoogleSettings": { - "Enable": false, - "Secret": "", - "Id": "", - "Scope": "", - "AuthEndpoint": "", - "TokenEndpoint": "", - "UserApiEndpoint": "" - }, - "LdapSettings": { - "Enable": false, - "LdapServer": null, - "LdapPort": 389, - "BaseDN": null, - "BindUsername": null, - "BindPassword": null, - "FirstNameAttribute": null, - "LastNameAttribute": null, - "EmailAttribute": null, - "UsernameAttribute": null, - "IdAttribute": null, - "QueryTimeout": 60 } } \ No newline at end of file From 1e4c52a70eab31ecbe50aa642e7f20a90eb913ac Mon Sep 17 00:00:00 2001 From: hmhealey Date: Mon, 11 Jan 2016 12:35:48 -0500 Subject: [PATCH 36/61] Fixed developer errors storing invalid error objects --- web/templates/head.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/templates/head.html b/web/templates/head.html index 70c94e8ff8..08d8726ea4 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -98,7 +98,7 @@ }); if (window.mm_config.EnableDeveloper === 'true') { - window.ErrorStore.storeLastError('DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'); + window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'}); window.ErrorStore.emitChange(); } } From fff7143a3175458d5a938cc91973ba475dc45456 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Mon, 11 Jan 2016 09:42:11 -0800 Subject: [PATCH 37/61] Changed statistics to show usernames with an email tooltip --- .../admin_console/team_analytics.jsx | 44 +++++++++++++++++-- web/react/components/channel_header.jsx | 2 +- web/react/components/time_since.jsx | 3 +- web/react/utils/constants.jsx | 3 +- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx index e28699d3cd..fe7230946b 100644 --- a/web/react/components/admin_console/team_analytics.jsx +++ b/web/react/components/admin_console/team_analytics.jsx @@ -3,8 +3,12 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; +import Constants from '../../utils/constants.jsx'; import LineChart from './line_chart.jsx'; +var Tooltip = ReactBootstrap.Tooltip; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; + export default class TeamAnalytics extends React.Component { constructor(props) { super(props); @@ -314,9 +318,25 @@ export default class TeamAnalytics extends React.Component { { this.state.recent_active_users.map((user) => { + const tooltip = ( + + {user.email} + + ); + return ( - - {user.email} + + + + + + {Utils.displayDateTime(user.last_activity_at)} ); @@ -347,9 +367,25 @@ export default class TeamAnalytics extends React.Component { { this.state.newly_created_users.map((user) => { + const tooltip = ( + + {user.email} + + ); + return ( - - {user.email} + + + + + + {Utils.displayDateTime(user.create_at)} ); diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 59ceb038e9..f64834775b 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -379,7 +379,7 @@ export default class ChannelHeader extends React.Component {
    diff --git a/web/react/components/time_since.jsx b/web/react/components/time_since.jsx index cffff6ee7c..32947bd60b 100644 --- a/web/react/components/time_since.jsx +++ b/web/react/components/time_since.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import Constants from '../utils/constants.jsx'; import * as Utils from '../utils/utils.jsx'; var Tooltip = ReactBootstrap.Tooltip; @@ -30,7 +31,7 @@ export default class TimeSince extends React.Component { return ( diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 0298ce5332..f3fca0f7e9 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -443,5 +443,6 @@ export default { label: 'embed_preview', description: 'Show preview snippet of links below message' } - } + }, + OVERLAY_TIME_DELAY: 400 }; From b94aad1fd35773be749ec67c0c0c68f0a7ec38ea Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 12:32:28 -0600 Subject: [PATCH 38/61] Removing react debugging --- Makefile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1d7fbea5a3..9fd74b959f 100644 --- a/Makefile +++ b/Makefile @@ -126,10 +126,6 @@ package: cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/ cp -RL web/templates $(DIST_PATH)/web - cp -L web/static/js/react-0.14.3.js $(DIST_PATH)/web/static/js/ - cp -L web/static/js/react-dom-0.14.3.js $(DIST_PATH)/web/static/js/ - cp -L web/static/js/react-bootstrap-0.28.1.js $(DIST_PATH)/web/static/js/ - mkdir -p $(DIST_PATH)/api cp -RL api/templates $(DIST_PATH)/api @@ -140,11 +136,11 @@ package: mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js - #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html - #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html - #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html + sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html From af040d47230324a185e72206b878a7e508d0061e Mon Sep 17 00:00:00 2001 From: Eric Sethna Date: Mon, 11 Jan 2016 11:57:01 -0700 Subject: [PATCH 39/61] Update test-attachments.md Update test doc after changes were made to how some audio and video files display and preview. --- doc/developer/tests/test-attachments.md | 27 ++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/doc/developer/tests/test-attachments.md b/doc/developer/tests/test-attachments.md index 71cc496ce7..e2fda0eb60 100644 --- a/doc/developer/tests/test-attachments.md +++ b/doc/developer/tests/test-attachments.md @@ -29,7 +29,7 @@ Expected: Scaled thumbnail & preview window **GIF** `Images/GIF.gif` -Expected: Scaled thumbnail & preview window. Click to play GIF. +Expected: Scaled thumbnail & preview window. GIF should auto-play in the preview window. [Permalink](https://pre-release.mattermost.com/core/pl/j49fowdkstr57g3ed9bgpfoo5w) **TIFF** @@ -72,37 +72,37 @@ Expected: Generic Word thumbnail & preview window. **MP4** `Videos/MP4.mp4` -Expected: Generic video thumbnail & playable preview window +Expected: Generic video thumbnail & playable preview window. View Permalink. [Permalink](https://pre-release.mattermost.com/core/pl/5dx5qx9t9brqfnhohccxjynx7c) **AVI** `Videos/AVI.avi` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/qwn9eiy7j3rkjyruxhcugpogdw) **MKV** `Videos/MKV.mkv` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/tszyjkr1cidhxjgiusa4mde3ja) **MOV** `Videos/MOV.mov` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/ienzppz5i3f7tbt5jiujn8uuir) **MPG** `Videos/MPG.mpg` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/skggdq1hfpritc6c88bi481p5a) **Webm** `Videos/WEBM.webm` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/7h8tysuxgfgsxeht3sbn7e4h6y) **WMV** `Videos/WMV.wmv` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/kaom7j7uyjra7bzhrre6qwdrbw) ### Audio @@ -112,7 +112,6 @@ Expected: View Permalink. Expected depends on the operating system, browser and Expected: Generic audio thumbnail & playable preview window [Permalink](https://pre-release.mattermost.com/core/pl/if4gn8dbrjgx8fmqmkukzefyme) - **M4A** `Audio/M4a.m4a` Expected: Generic audio thumbnail & playable preview window @@ -120,25 +119,25 @@ Expected: Generic audio thumbnail & playable preview window **AAC** `Audio/AAC.aac` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/3naoy5pr5tydbk1m6yo1ast9ny) **FLAC** `Audio/FLAC.flac` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/kf4cmy44dfya5efmse7rg43eih) **OGG** `Audio/OGG.ogg` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/dezrcpbxapyexe77rjuzkrp63r) **WAV** `Audio/WAV.wav` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/pdkxx1udepdnbmi9j8kyas5xbh) **WMA** `Audio/WMA.wma` -Expected: View Permalink. Expected depends on the operating system, browser and plugins. +Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins. [Permalink](https://pre-release.mattermost.com/core/pl/756wrmdd57dcig3m4emypp6i1h) From 5083a3947ac792bf668a3c0af545b86c3331a748 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 13:04:55 -0600 Subject: [PATCH 40/61] Release 1.4 rc1 --- docker/1.4/Dockerfile | 49 ++++++++ docker/1.4/Dockerrun.aws.zip | Bin 0 -> 1043 bytes .../.ebextensions/01_files.config | 14 +++ docker/1.4/Dockerrun.aws/Dockerrun.aws.json | 13 ++ docker/1.4/README.md | 23 ++++ docker/1.4/config_docker.json | 111 ++++++++++++++++++ docker/1.4/docker-entry.sh | 111 ++++++++++++++++++ model/version.go | 1 + 8 files changed, 322 insertions(+) create mode 100644 docker/1.4/Dockerfile create mode 100644 docker/1.4/Dockerrun.aws.zip create mode 100644 docker/1.4/Dockerrun.aws/.ebextensions/01_files.config create mode 100755 docker/1.4/Dockerrun.aws/Dockerrun.aws.json create mode 100644 docker/1.4/README.md create mode 100644 docker/1.4/config_docker.json create mode 100755 docker/1.4/docker-entry.sh diff --git a/docker/1.4/Dockerfile b/docker/1.4/Dockerfile new file mode 100644 index 0000000000..0e8b8853f7 --- /dev/null +++ b/docker/1.4/Dockerfile @@ -0,0 +1,49 @@ +# Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +# See License.txt for license information. +FROM ubuntu:14.04 + +# +# Install SQL +# + +ENV MYSQL_ROOT_PASSWORD=mostest +ENV MYSQL_USER=mmuser +ENV MYSQL_PASSWORD=mostest +ENV MYSQL_DATABASE=mattermost_test + +RUN groupadd -r mysql && useradd -r -g mysql mysql + +RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5 + +ENV MYSQL_MAJOR 5.6 +ENV MYSQL_VERSION 5.6.25 + +RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list + +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install perl wget mysql-server \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql + +RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf + +VOLUME /var/lib/mysql +# --------------------------------------------------------------------------------------------------------------------- + +WORKDIR /mattermost + +# Copy over files +RUN wget --no-check-certificate https://s3.amazonaws.com/mattermost-travis-master/mattermost.tar.gz +RUN tar -zxvf mattermost.tar.gz --strip-components=1 && rm mattermost.tar.gz +ADD config_docker.json / +ADD docker-entry.sh / + +RUN chmod +x /docker-entry.sh +ENTRYPOINT /docker-entry.sh + +# Create default storage directory +RUN mkdir /mattermost-data/ + +# Ports +EXPOSE 80 diff --git a/docker/1.4/Dockerrun.aws.zip b/docker/1.4/Dockerrun.aws.zip new file mode 100644 index 0000000000000000000000000000000000000000..272ea9c546726b6a6bf3c766e285150e18a89b41 GIT binary patch literal 1043 zcmWIWW@h1H00FBEZ4WR5O7JlVFu3F=XQvhwmFDRsmKW=XM({9L|Ct;n55!*?MHm8b z>XpIJtCyOTT2YdkSDcxj2Qf(r%_I>94j@E#g*E|`3=HGbGILUk^^)`R(lXOQPEdwy46V{CH;nEQ}XK0PHoB4nUgOazP>bZ1GA65)BKhn=j|qx zv1^1Xrgqy@#a-l@pWSYFa@W=M3#;xk#2g7^54~&>pBw#F!+hBlN1>CudpxK8a6dKO zXO4Bnn*fa;N`H^8`Kyrk`s#hoThE?XpI0_iW?g*qb|X)=S>iFzlrR%Rt1d^go<)+j zzhhp1(bu%MFSyv!oUXF(P(sPCyS*1z+>?>vonR{~zLdr9P0X!_oB`hK9DKe$M|K0_ zBbt!`9NLgD@4e(-Z_KT0YGne&^ZRabDT_!x zF_3O`-f++JV)mq(O^(Ver{8EKoL;t4ce=;I6VFd?5qf`Xk8ZzO@N^iXt4;DP1`ON*xzOJ~PKckFHtCVMH*n@oi(S}iA2GG}+fH=UL zkx7&pR|@5Urd0-qw~intiob#Bl>?q$@tGsTfNYKphB@eImxBS8b|I!BQZ90QX(O4s zq_K^FsTk>)1D=kNEdfP6a_}f1S@O1V6)>5g1_25Wnh-eP2_e9nl?@bZEI=3w)VqKQ G!~+1DdrcYu literal 0 HcmV?d00001 diff --git a/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config b/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config new file mode 100644 index 0000000000..7f40a8b34f --- /dev/null +++ b/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config @@ -0,0 +1,14 @@ +files: + "/etc/nginx/conf.d/proxy.conf": + mode: "000755" + owner: root + group: root + content: | + client_max_body_size 50M; + "/opt/elasticbeanstalk/hooks/appdeploy/post/init.sh": + mode: "000755" + owner: root + group: root + content: | + #!/usr/bin/env bash + gpasswd -a ec2-user docker diff --git a/docker/1.4/Dockerrun.aws/Dockerrun.aws.json b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json new file mode 100755 index 0000000000..6549615894 --- /dev/null +++ b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json @@ -0,0 +1,13 @@ +{ + "AWSEBDockerrunVersion": "1", + "Image": { + "Name": "mattermost/platform:1.4", + "Update": "true" + }, + "Ports": [ + { + "ContainerPort": "80" + } + ], + "Logging": "/var/log/" +} diff --git a/docker/1.4/README.md b/docker/1.4/README.md new file mode 100644 index 0000000000..f737a15549 --- /dev/null +++ b/docker/1.4/README.md @@ -0,0 +1,23 @@ +Mattermost +========== + +http:/mattermost.org + +Mattermost is an open-source team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search. + +Installing Mattermost +===================== + +To run an instance of the latest version of mattermost on your local machine you can run: + +`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform` + +To update this image to the latest version you can run: + +`docker pull mattermost/platform` + +To run an instance of the latest code from the master branch on GitHub you can run: + +`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev` + +Any questions, please visit http://forum.mattermost.org diff --git a/docker/1.4/config_docker.json b/docker/1.4/config_docker.json new file mode 100644 index 0000000000..1aa2ee843f --- /dev/null +++ b/docker/1.4/config_docker.json @@ -0,0 +1,111 @@ +{ + "ServiceSettings": { + "ListenAddress": ":80", + "MaximumLoginAttempts": 10, + "SegmentDeveloperKey": "", + "GoogleDeveloperKey": "", + "EnableOAuthServiceProvider": false, + "EnableIncomingWebhooks": false, + "EnableOutgoingWebhooks": false, + "EnablePostUsernameOverride": false, + "EnablePostIconOverride": false, + "EnableTesting": false, + "EnableDeveloper": false, + "EnableSecurityFixAlert": true, + "SessionLengthWebInDays" : 30, + "SessionLengthMobileInDays" : 30, + "SessionLengthSSOInDays" : 30, + "SessionCacheInMinutes" : 10 + }, + "TeamSettings": { + "SiteName": "Mattermost", + "MaxUsersPerTeam": 50, + "EnableTeamCreation": true, + "EnableUserCreation": true, + "RestrictCreationToDomains": "", + "RestrictTeamNames": true, + "EnableTeamListing": false + }, + "SqlSettings": { + "DriverName": "mysql", + "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8", + "DataSourceReplicas": [], + "MaxIdleConns": 10, + "MaxOpenConns": 10, + "Trace": false, + "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg" + }, + "LogSettings": { + "EnableConsole": false, + "ConsoleLevel": "INFO", + "EnableFile": true, + "FileLevel": "INFO", + "FileFormat": "", + "FileLocation": "" + }, + "FileSettings": { + "DriverName": "local", + "Directory": "/mattermost/data/", + "EnablePublicLink": true, + "PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip", + "ThumbnailWidth": 120, + "ThumbnailHeight": 100, + "PreviewWidth": 1024, + "PreviewHeight": 0, + "ProfileWidth": 128, + "ProfileHeight": 128, + "InitialFont": "luximbi.ttf", + "AmazonS3AccessKeyId": "", + "AmazonS3SecretAccessKey": "", + "AmazonS3Bucket": "", + "AmazonS3Region": "", + "AmazonS3Endpoint": "", + "AmazonS3BucketEndpoint": "", + "AmazonS3LocationConstraint": false, + "AmazonS3LowercaseBucket": false + }, + "EmailSettings": { + "EnableSignUpWithEmail": true, + "SendEmailNotifications": false, + "RequireEmailVerification": false, + "FeedbackName": "", + "FeedbackEmail": "", + "SMTPUsername": "", + "SMTPPassword": "", + "SMTPServer": "", + "SMTPPort": "", + "ConnectionSecurity": "", + "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS", + "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL", + "SendPushNotifications": false, + "PushNotificationServer": "" + }, + "RateLimitSettings": { + "EnableRateLimiter": true, + "PerSec": 10, + "MemoryStoreSize": 10000, + "VaryByRemoteAddr": true, + "VaryByHeader": "" + }, + "PrivacySettings": { + "ShowEmailAddress": true, + "ShowFullName": true + }, + "SupportSettings": { + "TermsOfServiceLink": "/static/help/terms.html", + "PrivacyPolicyLink": "/static/help/privacy.html", + "AboutLink": "/static/help/about.html", + "HelpLink": "/static/help/help.html", + "ReportAProblemLink": "/static/help/report_problem.html", + "SupportEmail": "feedback@mattermost.com" + }, + "GitLabSettings": { + "Enable": false, + "Secret": "", + "Id": "", + "Scope": "", + "AuthEndpoint": "", + "TokenEndpoint": "", + "UserApiEndpoint": "" + } +} diff --git a/docker/1.4/docker-entry.sh b/docker/1.4/docker-entry.sh new file mode 100755 index 0000000000..6bd2a1263b --- /dev/null +++ b/docker/1.4/docker-entry.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +# See License.txt for license information. + +mkdir -p web/static/js + +echo "127.0.0.1 dockerhost" >> /etc/hosts +/etc/init.d/networking restart + +echo configuring mysql + +# SQL!!! +set -e + +get_option () { + local section=$1 + local option=$2 + local default=$3 + ret=$(my_print_defaults $section | grep '^--'${option}'=' | cut -d= -f2-) + [ -z $ret ] && ret=$default + echo $ret +} + + +# Get config +DATADIR="$("mysqld" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')" +SOCKET=$(get_option mysqld socket "$DATADIR/mysql.sock") +PIDFILE=$(get_option mysqld pid-file "/var/run/mysqld/mysqld.pid") + +if [ ! -d "$DATADIR/mysql" ]; then + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then + echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set' + echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?' + exit 1 + fi + + mkdir -p "$DATADIR" + chown -R mysql:mysql "$DATADIR" + + echo 'Running mysql_install_db' + mysql_install_db --user=mysql --datadir="$DATADIR" --rpm --keep-my-cnf + echo 'Finished mysql_install_db' + + mysqld --user=mysql --datadir="$DATADIR" --skip-networking & + for i in $(seq 30 -1 0); do + [ -S "$SOCKET" ] && break + echo 'MySQL init process in progress...' + sleep 1 + done + if [ $i = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + # These statements _must_ be on individual lines, and _must_ end with + # semicolons (no line breaks or comments are permitted). + # TODO proper SQL escaping on ALL the things D: + + tempSqlFile=$(mktemp /tmp/mysql-first-time.XXXXXX.sql) + cat > "$tempSqlFile" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + + DELETE FROM mysql.user ; + CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ; + DROP DATABASE IF EXISTS test ; + EOSQL + + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile" + fi + + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" >> "$tempSqlFile" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" >> "$tempSqlFile" + fi + fi + + echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile" + + mysql -uroot < "$tempSqlFile" + + rm -f "$tempSqlFile" + kill $(cat $PIDFILE) + for i in $(seq 30 -1 0); do + [ -f "$PIDFILE" ] || break + echo 'MySQL init process in progress...' + sleep 1 + done + if [ $i = 0 ]; then + echo >&2 'MySQL hangs during init process.' + exit 1 + fi + echo 'MySQL init process done. Ready for start up.' +fi + +chown -R mysql:mysql "$DATADIR" + +mysqld & + +sleep 5 + +# ------------------------ + +echo starting platform +cd /mattermost/bin +./platform -config=/config_docker.json diff --git a/model/version.go b/model/version.go index 142ddb371a..88334ceea9 100644 --- a/model/version.go +++ b/model/version.go @@ -12,6 +12,7 @@ import ( // It should be maitained in chronological order with most current // release at the front of the list. var versions = []string{ + "1.4.0", "1.3.0", "1.2.1", "1.2.0", From 51bd6808bf7ce9fff438e154271e33978e1cd9bc Mon Sep 17 00:00:00 2001 From: lfbrock Date: Mon, 11 Jan 2016 14:19:47 -0500 Subject: [PATCH 41/61] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b4857421..59aca04363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,9 +112,12 @@ The following options are not present in the System Console, and can be modified - Searching for a phrase in quotations returns more than just the phrase on Mattermost installations with a Postgres database - Deleted/Archived channels are not removed from the "More" menu of the person that deleted/archived the channel until after refresh - Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks +- Searching for a username or hashtag containing a dot returns replaces the dot with an "or" +- Browser tab * to indicate Unreads does not work on IE11 - Hashtags less than three characters long are not searchable - After deactivating a team member, the person remains in the channel counter - Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search +- Security tab > Active Sessions reports iOS devices as "unknown" #### Contributors From 0b9570d059aa694fc1ce02863de5fee7e461b2dc Mon Sep 17 00:00:00 2001 From: lfbrock Date: Mon, 11 Jan 2016 14:22:07 -0500 Subject: [PATCH 42/61] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59aca04363..087439923b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,7 +113,6 @@ The following options are not present in the System Console, and can be modified - Deleted/Archived channels are not removed from the "More" menu of the person that deleted/archived the channel until after refresh - Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks - Searching for a username or hashtag containing a dot returns replaces the dot with an "or" -- Browser tab * to indicate Unreads does not work on IE11 - Hashtags less than three characters long are not searchable - After deactivating a team member, the person remains in the channel counter - Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search From 22f714bd029a25230b4e1caeb12e7fce512834d2 Mon Sep 17 00:00:00 2001 From: it33 Date: Mon, 11 Jan 2016 11:22:13 -0800 Subject: [PATCH 43/61] Adding push-test.mattermost.com --- web/react/components/admin_console/email_settings.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 91d73dccde..193fd4147a 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -581,12 +581,12 @@ export default class EmailSettings extends React.Component { className='form-control' id='PushNotificationServer' ref='PushNotificationServer' - placeholder='E.g.: "https://push.mattermost.com"' + placeholder='E.g.: "https://push-test.mattermost.com"' defaultValue={this.props.config.EmailSettings.PushNotificationServer} onChange={this.handleChange} disabled={!this.state.sendPushNotifications} /> -

    {'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}

    +

    {'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}

    From 63b5182150ac03af41700e016e048cffcce293fc Mon Sep 17 00:00:00 2001 From: lfbrock Date: Mon, 11 Jan 2016 14:25:28 -0500 Subject: [PATCH 44/61] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 087439923b..0a4447a025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,7 +112,7 @@ The following options are not present in the System Console, and can be modified - Searching for a phrase in quotations returns more than just the phrase on Mattermost installations with a Postgres database - Deleted/Archived channels are not removed from the "More" menu of the person that deleted/archived the channel until after refresh - Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks -- Searching for a username or hashtag containing a dot returns replaces the dot with an "or" +- Searching for a username or hashtag containing a dot returns a search where the dot is replaced with the "or" operator - Hashtags less than three characters long are not searchable - After deactivating a team member, the person remains in the channel counter - Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search From 7610e2ae000fdac159d6bf72e4cdada98d5bf0bd Mon Sep 17 00:00:00 2001 From: Eric Sethna Date: Mon, 11 Jan 2016 13:41:47 -0700 Subject: [PATCH 45/61] Update test-markdown-basics.md Add carriage return tests --- doc/developer/tests/test-markdown-basics.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/developer/tests/test-markdown-basics.md b/doc/developer/tests/test-markdown-basics.md index 7a46adeb09..b0288db2d6 100644 --- a/doc/developer/tests/test-markdown-basics.md +++ b/doc/developer/tests/test-markdown-basics.md @@ -18,6 +18,18 @@ Normal Text_ _Normal Text _Normal Text* +### Carriage Return + +**The following text should render as:** +Line #1 followed by Line #2 +Line #2 followed by one blank line + +Line #3 followed by one blank line + + +Line #4 following one blank line + + ### Code Blocks ``` From a457a83c6152049e2a1871944bf8a9e8c688d981 Mon Sep 17 00:00:00 2001 From: lfbrock Date: Mon, 11 Jan 2016 16:50:15 -0500 Subject: [PATCH 46/61] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4447a025..5849b30b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ The following options are not present in the System Console, and can be modified - After deactivating a team member, the person remains in the channel counter - Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search - Security tab > Active Sessions reports iOS devices as "unknown" +- Getting a permalink for the second message or later consecutively sent in a group by the same author displaces the copy link popover or causes an error #### Contributors From 16263f4e260765b3f48cc98f72c7a50cbb40278c Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 16:52:30 -0600 Subject: [PATCH 47/61] Fixing dockerfile --- docker/1.4/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/1.4/Dockerfile b/docker/1.4/Dockerfile index 0e8b8853f7..8dcea3927a 100644 --- a/docker/1.4/Dockerfile +++ b/docker/1.4/Dockerfile @@ -34,8 +34,8 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -RUN wget --no-check-certificate https://s3.amazonaws.com/mattermost-travis-master/mattermost.tar.gz -RUN tar -zxvf mattermost.tar.gz --strip-components=1 && rm mattermost.tar.gz +ADD https://github.com/mattermost/platform/releases/download/v1.4.0/mattermost.tar.gz / +RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / From 1763ff6d72bae7dc57475dc73a03a7c0f12acc90 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 12 Jan 2016 08:02:38 -0500 Subject: [PATCH 48/61] Support old oauth routes --- api/user.go | 2 +- web/web.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/user.go b/api/user.go index d4c7fcaf59..00d637ec27 100644 --- a/api/user.go +++ b/api/user.go @@ -1790,7 +1790,7 @@ func GetAuthorizationCode(c *Context, service, teamName string, props map[string props["team"] = teamName state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) - redirectUri := c.GetSiteURL() + "/" + service + "/complete" + redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8) authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state) diff --git a/web/web.go b/web/web.go index bf1208adcb..337ed311d7 100644 --- a/web/web.go +++ b/web/web.go @@ -70,6 +70,8 @@ func InitWeb() { mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET") mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET") mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET") + mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") + mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET") @@ -711,7 +713,7 @@ func completeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") - uri := c.GetSiteURL() + "/" + service + "/complete" + uri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8) if body, team, props, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil { c.Err = err From d1eb3eef309fb6b859511123cf2507e92b3ced49 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 12 Jan 2016 08:04:55 -0500 Subject: [PATCH 49/61] Add removal comments --- web/web.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/web.go b/web/web.go index 337ed311d7..c0fbb9808f 100644 --- a/web/web.go +++ b/web/web.go @@ -70,8 +70,8 @@ func InitWeb() { mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET") mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET") mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET") - mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") - mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") + mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8) + mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8) mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET") From 85e9797c0c916afa104703be0e59a64dca264162 Mon Sep 17 00:00:00 2001 From: lfbrock Date: Tue, 12 Jan 2016 10:47:57 -0500 Subject: [PATCH 50/61] Update test-markdown-basics.md --- doc/developer/tests/test-markdown-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/developer/tests/test-markdown-basics.md b/doc/developer/tests/test-markdown-basics.md index 7a46adeb09..11eb2bb055 100644 --- a/doc/developer/tests/test-markdown-basics.md +++ b/doc/developer/tests/test-markdown-basics.md @@ -119,7 +119,7 @@ Text below line **The following markdown should render within the block quote:** > #### Heading 4 -> _Italics_, *Italics*, **Bold**, ***Bold-italics***, **Bold-italics_**, ~~Strikethrough~~ +> _Italics_, *Italics*, **Bold**, ***Bold-italics***, **_Bold-italics_**, ~~Strikethrough~~ > :) :-) ;) :-O :bamboo: :gift_heart: :dolls: **The following text should render in two block quotes separated by one line of text:** From 6ce6115100aea3b11e113cc805abfaff89af12c4 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 12 Jan 2016 14:11:18 -0600 Subject: [PATCH 51/61] Update docker to 1.4 RC2 --- docker/1.4/Dockerfile | 2 +- docker/1.4/Dockerrun.aws.zip | Bin 1043 -> 1046 bytes docker/1.4/Dockerrun.aws/Dockerrun.aws.json | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/1.4/Dockerfile b/docker/1.4/Dockerfile index 8dcea3927a..97a863897d 100644 --- a/docker/1.4/Dockerfile +++ b/docker/1.4/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v1.4.0/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v1.4.0-rc1/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / diff --git a/docker/1.4/Dockerrun.aws.zip b/docker/1.4/Dockerrun.aws.zip index 272ea9c546726b6a6bf3c766e285150e18a89b41..0ea2ad97250113e967f1663d11adb50822afc928 100644 GIT binary patch delta 421 zcmbQtF^z*Kz?+$civa{?kfgU_mn3huL2d8YW>7T@Wc?;CR3-o4kkFQVteO0p6@^pm<;b N!dONIhQ&-E9ssyNjSm0- delta 418 zcmbQnF`0uWz?+$civa|zGA8nM@L5U6jGBfC08 zZsM(5%w8GVleaUP*4qGuK*oWL((T#Jb;v=$wfbN^M{sxc0jEnpwO9lluQ=(5-PJkx z#&3pCj?=%pGuK=#cyC$$>!r%+gxdXY%&lu`Wdg;^j-)JP9Uba$qy2rv3&rfd=dVgz=ZogXabBEiCwKmFy+RhIqp82257~PYz`h^E? z>^iXt4;DP1`ON*xzOJ~PKckFHtCVMH*avvCb9}U66qo@FflELf;LXS+$_xvU$s3uB z#9%>!CiRoaK@1im5Gh!AO!jB?SA+&g7_t;FWIW)OP2SIJDF+Lq0B=?{P$aMbVJss9 J!vZD{4*>N(kOcq$ diff --git a/docker/1.4/Dockerrun.aws/Dockerrun.aws.json b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json index 6549615894..2d107d41ab 100755 --- a/docker/1.4/Dockerrun.aws/Dockerrun.aws.json +++ b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json @@ -1,7 +1,7 @@ { "AWSEBDockerrunVersion": "1", "Image": { - "Name": "mattermost/platform:1.4", + "Name": "mattermost/platform:1.4rc2", "Update": "true" }, "Ports": [ From 837de1e0c871342a9c87d04ec08636f0847f5bc7 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 12 Jan 2016 16:40:18 -0600 Subject: [PATCH 52/61] Fixing docker files for 1.4 RC2 --- docker/1.4/Dockerfile | 2 +- docker/1.4/Dockerrun.aws.zip | Bin 1046 -> 1043 bytes docker/1.4/Dockerrun.aws/Dockerrun.aws.json | 2 +- docker/1.4/config_docker.json | 21 ++------------------ 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/docker/1.4/Dockerfile b/docker/1.4/Dockerfile index 97a863897d..1f0acdfc97 100644 --- a/docker/1.4/Dockerfile +++ b/docker/1.4/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v1.4.0-rc1/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v1.4.0-rc2/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / diff --git a/docker/1.4/Dockerrun.aws.zip b/docker/1.4/Dockerrun.aws.zip index 0ea2ad97250113e967f1663d11adb50822afc928..91e9a9a2dfa519b90f9d51428b785e47220645e3 100644 GIT binary patch delta 429 zcmbQnF`0uez?+$civa|hle^p}@+nExHB1dNJCYgpl~IHNP1ef*Lso==0|>!-r`WSw zLiA3&b&Kg@%j9j0X7wP`GJzOmnr_c-u0sw2uGI(YIfA>h4>(==sl_7bc*RLa?5@ti zH-0mGa-9C%ow?>}!F$W{UoTZoC)DnLV{TnjD-$T5-*<~kSw!-Qfpn|$hI^hDvnSPT za#UtH{YE3<^s<$@(>)fRcz$||(ED3^bo9o#UeoqrePcFkAxS0B=SnQD#K2Oy107 zAOQ;(49VY2c4(4_K$#rK?5hY3l`x1-h~&i<9S^vbllL=Q%E3Y^z?+o~6dx==7|Y1O Juz(4~0{}b?lV$(_ delta 432 zcmbQtF^z*Sz?+$civa{?KGm}N@XS4I&AG+8eL3|SEd4j=^Uonp^^ z#&ilu@5EcTm^=z6Z(}s8-wiZv1rUQw)9u;Kb;v=$wfbN^M{sxc5vNN(wO9lluQ=(j z#ZFHsQ}K^NsqSUN+7DY|`^;Y28AeO$FJGx9x0g1wFil58BTBe_t?1 zgJaskO;WpJ&M6#>JFk3}|4EGGoTAUJW}=qDTUY!6`jnkRbi;>;*}#yv3d8~4j7*}; zh>)4QfyqDu7Bm==KbY*$BoW~<*^k**5gIUI5S Date: Tue, 12 Jan 2016 16:42:31 -0600 Subject: [PATCH 53/61] Fixing config file --- docker/1.4/config_docker.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docker/1.4/config_docker.json b/docker/1.4/config_docker.json index a35abb9da8..1aa2ee843f 100644 --- a/docker/1.4/config_docker.json +++ b/docker/1.4/config_docker.json @@ -10,7 +10,12 @@ "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, "EnableTesting": false, - "EnableSecurityFixAlert": true + "EnableDeveloper": false, + "EnableSecurityFixAlert": true, + "SessionLengthWebInDays" : 30, + "SessionLengthMobileInDays" : 30, + "SessionLengthSSOInDays" : 30, + "SessionCacheInMinutes" : 10 }, "TeamSettings": { "SiteName": "Mattermost", @@ -53,7 +58,11 @@ "AmazonS3AccessKeyId": "", "AmazonS3SecretAccessKey": "", "AmazonS3Bucket": "", - "AmazonS3Region": "" + "AmazonS3Region": "", + "AmazonS3Endpoint": "", + "AmazonS3BucketEndpoint": "", + "AmazonS3LocationConstraint": false, + "AmazonS3LowercaseBucket": false }, "EmailSettings": { "EnableSignUpWithEmail": true, @@ -82,6 +91,14 @@ "ShowEmailAddress": true, "ShowFullName": true }, + "SupportSettings": { + "TermsOfServiceLink": "/static/help/terms.html", + "PrivacyPolicyLink": "/static/help/privacy.html", + "AboutLink": "/static/help/about.html", + "HelpLink": "/static/help/help.html", + "ReportAProblemLink": "/static/help/report_problem.html", + "SupportEmail": "feedback@mattermost.com" + }, "GitLabSettings": { "Enable": false, "Secret": "", From 9110dd54a15f3d0fcf6f60936e01d816b667b93c Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 4 Jan 2016 12:44:22 -0500 Subject: [PATCH 54/61] Added license validation and settings --- .gitignore | 1 + Makefile | 5 + api/api.go | 1 + api/context.go | 1 + api/file.go | 20 +- api/license.go | 95 +++++++ mattermost.go | 2 + model/license.go | 68 +++++ utils/license.go | 152 ++++++++++++ web/react/components/about_build_modal.jsx | 24 +- .../admin_console/admin_controller.jsx | 3 + .../admin_console/admin_sidebar.jsx | 32 +++ .../admin_console/ldap_settings.jsx | 32 ++- .../admin_console/license_settings.jsx | 232 ++++++++++++++++++ web/react/components/file_upload.jsx | 27 +- web/react/utils/client.jsx | 35 +++ web/react/utils/utils.jsx | 31 ++- .../sass/partials/_admin-console.scss | 5 +- web/templates/head.html | 1 + web/web.go | 2 +- 20 files changed, 736 insertions(+), 33 deletions(-) create mode 100644 api/license.go create mode 100644 model/license.go create mode 100644 utils/license.go create mode 100644 web/react/components/admin_console/license_settings.jsx diff --git a/.gitignore b/.gitignore index dab6b83732..7f60a02ab4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ logs node_modules dist npm-debug.log +bin web/static/js/bundle*.js web/static/js/bundle*.js.map diff --git a/Makefile b/Makefile index 9fd74b959f..49bf7dc2d9 100644 --- a/Makefile +++ b/Makefile @@ -239,6 +239,7 @@ clean: stop-docker rm -f .prepare-go .prepare-jsx nuke: | clean clean-docker + rm -rf bin rm -rf data .prepare-go: @@ -266,6 +267,9 @@ run: start-docker .prepare-go .prepare-jsx jq -s '.[0] * .[1]' ./config/config.json $(ENTERPRISE_DIR)/config/enterprise-config-additions.json > config.json.tmp; \ mv config.json.tmp ./config/config.json; \ sed -e '/\/\/ENTERPRISE_IMPORTS/ {' -e 'r $(ENTERPRISE_DIR)/imports' -e 'd' -e '}' -i'.bak' mattermost.go; \ + sed -i'.bak' 's|_BUILD_ENTERPRISE_READY_|true|g' ./model/version.go; \ + else \ + sed -i'.bak' 's|_BUILD_ENTERPRISE_READY_|false|g' ./model/version.go; \ fi @echo Starting go web server @@ -299,6 +303,7 @@ stop: @if [ "$(BUILD_ENTERPRISE)" = "true" ] && [ -d "$(ENTERPRISE_DIR)" ]; then \ mv ./config/config.json.bak ./config/config.json 2> /dev/null || true; \ mv ./mattermost.go.bak ./mattermost.go 2> /dev/null || true; \ + mv ./model/version.go.bak ./model/version.go 2> /dev/null || true; \ fi setup-mac: diff --git a/api/api.go b/api/api.go index a6bb229823..f29063fe1f 100644 --- a/api/api.go +++ b/api/api.go @@ -46,6 +46,7 @@ func InitApi() { InitOAuth(r) InitWebhook(r) InitPreference(r) + InitLicense(r) templatesDir := utils.FindDir("api/templates") l4g.Debug("Parsing server templates at %v", templatesDir) diff --git a/api/context.go b/api/context.go index 561884c145..e8ec6576d0 100644 --- a/api/context.go +++ b/api/context.go @@ -35,6 +35,7 @@ type Page struct { TemplateName string Props map[string]string ClientCfg map[string]string + ClientLicense map[string]string User *model.User Team *model.Team Channel *model.Channel diff --git a/api/file.go b/api/file.go index d023515af1..46e81691e3 100644 --- a/api/file.go +++ b/api/file.go @@ -541,12 +541,8 @@ func writeFile(f []byte, path string) *model.AppError { return model.NewAppError("writeFile", "Encountered an error writing to S3", err.Error()) } } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL { - if err := os.MkdirAll(filepath.Dir(utils.Cfg.FileSettings.Directory+path), 0774); err != nil { - return model.NewAppError("writeFile", "Encountered an error creating the directory for the new file", err.Error()) - } - - if err := ioutil.WriteFile(utils.Cfg.FileSettings.Directory+path, f, 0644); err != nil { - return model.NewAppError("writeFile", "Encountered an error writing to local server storage", err.Error()) + if err := writeFileLocally(f, utils.Cfg.FileSettings.Directory+path); err != nil { + return err } } else { return model.NewAppError("writeFile", "File storage not configured properly. Please configure for either S3 or local server file storage.", "") @@ -555,6 +551,18 @@ func writeFile(f []byte, path string) *model.AppError { return nil } +func writeFileLocally(f []byte, path string) *model.AppError { + if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil { + return model.NewAppError("writeFile", "Encountered an error creating the directory for the new file", err.Error()) + } + + if err := ioutil.WriteFile(path, f, 0644); err != nil { + return model.NewAppError("writeFile", "Encountered an error writing to local server storage", err.Error()) + } + + return nil +} + func readFile(path string) ([]byte, *model.AppError) { if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 { diff --git a/api/license.go b/api/license.go new file mode 100644 index 0000000000..9ed2d2afbe --- /dev/null +++ b/api/license.go @@ -0,0 +1,95 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "bytes" + l4g "code.google.com/p/log4go" + "github.com/gorilla/mux" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "io" + "net/http" + "strings" +) + +func InitLicense(r *mux.Router) { + l4g.Debug("Initializing license api routes") + + sr := r.PathPrefix("/license").Subrouter() + sr.Handle("/add", ApiAdminSystemRequired(addLicense)).Methods("POST") + sr.Handle("/remove", ApiAdminSystemRequired(removeLicense)).Methods("POST") +} + +func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { + c.LogAudit("attempt") + err := r.ParseMultipartForm(model.MAX_FILE_SIZE) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + m := r.MultipartForm + + fileArray, ok := m.File["license"] + if !ok { + c.Err = model.NewAppError("addLicense", "No file under 'license' in request", "") + c.Err.StatusCode = http.StatusBadRequest + return + } + + if len(fileArray) <= 0 { + c.Err = model.NewAppError("addLicense", "Empty array under 'license' in request", "") + c.Err.StatusCode = http.StatusBadRequest + return + } + + fileData := fileArray[0] + + file, err := fileData.Open() + defer file.Close() + if err != nil { + c.Err = model.NewAppError("addLicense", "Could not open license file", err.Error()) + return + } + + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + + data := buf.Bytes() + + var license *model.License + if success, licenseStr := utils.ValidateLicense(data); success { + license = model.LicenseFromJson(strings.NewReader(licenseStr)) + + if ok := utils.SetLicense(license); !ok { + c.LogAudit("failed - expired or non-started license") + c.Err = model.NewAppError("addLicense", "License is either expired or has not yet started.", "") + return + } + + go func() { + if err := writeFileLocally(data, utils.LICENSE_FILE_LOC); err != nil { + l4g.Error("Could not save license file") + } + }() + } else { + c.LogAudit("failed - invalid license") + c.Err = model.NewAppError("addLicense", "Invalid license file", "") + return + } + + c.LogAudit("success") + w.Write([]byte(license.ToJson())) +} + +func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { + c.LogAudit("") + + utils.RemoveLicense() + + rdata := map[string]string{} + rdata["status"] = "ok" + w.Write([]byte(model.MapToJson(rdata))) +} diff --git a/mattermost.go b/mattermost.go index f86af76e36..f6abb90194 100644 --- a/mattermost.go +++ b/mattermost.go @@ -67,6 +67,8 @@ func main() { api.InitApi() web.InitWeb() + utils.LoadLicense() + if flagRunCmds { runCmds() } else { diff --git a/model/license.go b/model/license.go new file mode 100644 index 0000000000..7a955e1f82 --- /dev/null +++ b/model/license.go @@ -0,0 +1,68 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type License struct { + Id string `json:"id"` + IssuedAt int64 `json:"issued_at"` + StartsAt int64 `json:"starts_at"` + ExpiresAt int64 `json:"expires_at"` + Customer *Customer `json:"customer"` + Features *Features `json:"features"` +} + +type Customer struct { + Id string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Company string `json:"company"` + PhoneNumber string `json:"phone_number"` +} + +type Features struct { + Users int `json:"users"` + LDAP bool `json:"ldap"` + GoogleSSO bool `json:"google_sso"` +} + +func (l *License) IsExpired() bool { + now := GetMillis() + if l.ExpiresAt < now { + return true + } + return false +} + +func (l *License) IsStarted() bool { + now := GetMillis() + if l.StartsAt < now { + return true + } + return false +} + +func (l *License) ToJson() string { + b, err := json.Marshal(l) + if err != nil { + return "" + } else { + return string(b) + } +} + +func LicenseFromJson(data io.Reader) *License { + decoder := json.NewDecoder(data) + var o License + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/utils/license.go b/utils/license.go new file mode 100644 index 0000000000..1f8e24f32f --- /dev/null +++ b/utils/license.go @@ -0,0 +1,152 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "bytes" + "crypto" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "io" + "os" + "strconv" + "strings" + + l4g "code.google.com/p/log4go" + + "github.com/mattermost/platform/model" +) + +const ( + LICENSE_FILE_LOC = "./data/active.dat" +) + +var IsLicensed bool = false +var License *model.License = &model.License{} +var ClientLicense map[string]string = make(map[string]string) + +// test public key +var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3/k3Al9q1Xe+xngQ/yGn +0suaJopea3Cpf6NjIHdO8sYTwLlxqt0Mdb+qBR9LbCjZfcNmqc5mZONvsyCEoN/5 +VoLdlv1m9ao2BSAWphUxE2CPdUWdLOsDbQWliSc5//UhiYeR+67Xxon0Hg0LKXF6 +PumRIWQenRHJWqlUQZ147e7/1v9ySVRZksKpvlmMDzgq+kCH/uyM1uVP3z7YXhlN +K7vSSQYbt4cghvWQxDZFwpLlsChoY+mmzClgq+Yv6FLhj4/lk94twdOZau/AeZFJ +NxpC+5KFhU+xSeeklNqwCgnlOyZ7qSTxmdJHb+60SwuYnnGIYzLJhY4LYDr4J+KR +1wIDAQAB +-----END PUBLIC KEY-----`) + +func LoadLicense() { + file, err := os.Open(LICENSE_FILE_LOC) + if err != nil { + l4g.Warn("Unable to open/find license file") + return + } + defer file.Close() + + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + + if success, licenseStr := ValidateLicense(buf.Bytes()); success { + license := model.LicenseFromJson(strings.NewReader(licenseStr)) + if !license.IsExpired() && license.IsStarted() && license.StartsAt > License.StartsAt { + License = license + IsLicensed = true + ClientLicense = getClientLicense(license) + return + } + } + + l4g.Warn("No valid enterprise license found") +} + +func SetLicense(license *model.License) bool { + if !license.IsExpired() && license.IsStarted() { + License = license + IsLicensed = true + ClientLicense = getClientLicense(license) + return true + } + + return false +} + +func RemoveLicense() { + License = &model.License{} + IsLicensed = false + ClientLicense = getClientLicense(License) + + if err := os.Remove(LICENSE_FILE_LOC); err != nil { + l4g.Error("Unable to remove license file, err=%v", err.Error()) + } +} + +func ValidateLicense(signed []byte) (bool, string) { + decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed))) + + _, err := base64.StdEncoding.Decode(decoded, signed) + if err != nil { + l4g.Error("Encountered error decoding license, err=%v", err.Error()) + return false, "" + } + + if len(decoded) <= 256 { + l4g.Error("Signed license not long enough") + return false, "" + } + + // remove null terminator + if decoded[len(decoded)-1] == byte(0) { + decoded = decoded[:len(decoded)-1] + } + + plaintext := decoded[:len(decoded)-256] + signature := decoded[len(decoded)-256:] + + block, _ := pem.Decode(publicKey) + + public, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + l4g.Error("Encountered error signing license, err=%v", err.Error()) + return false, "" + } + + rsaPublic := public.(*rsa.PublicKey) + + h := sha256.New() + h.Write(plaintext) + d := h.Sum(nil) + + err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA256, d, signature) + if err != nil { + l4g.Error("Invalid signature, err=%v", err.Error()) + return false, "" + } + + return true, string(plaintext) +} + +func getClientLicense(l *model.License) map[string]string { + props := make(map[string]string) + + props["IsLicensed"] = strconv.FormatBool(IsLicensed) + + if IsLicensed { + props["Users"] = strconv.Itoa(l.Features.Users) + props["LDAP"] = strconv.FormatBool(l.Features.LDAP) + props["GoogleSSO"] = strconv.FormatBool(l.Features.GoogleSSO) + props["IssuedAt"] = strconv.FormatInt(l.IssuedAt, 10) + props["StartsAt"] = strconv.FormatInt(l.StartsAt, 10) + props["ExpiresAt"] = strconv.FormatInt(l.ExpiresAt, 10) + props["Name"] = l.Customer.Name + props["Email"] = l.Customer.Email + props["Company"] = l.Customer.Company + props["PhoneNumber"] = l.Customer.PhoneNumber + } + + return props +} diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx index 3143bec224..d542146320 100644 --- a/web/react/components/about_build_modal.jsx +++ b/web/react/components/about_build_modal.jsx @@ -15,6 +15,19 @@ export default class AboutBuildModal extends React.Component { render() { const config = global.window.mm_config; + const license = global.window.mm_license; + + let title = 'Team Edition'; + let licensee; + if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') { + title = 'Enterprise Edition'; + licensee = ( +
    +
    {'Licensed by:'}
    +
    {license.Company}
    +
    + ); + } return ( - {`Mattermost ${config.Version}`} + {'About Mattermost'} +
    +
    {'Edition:'}
    +
    {`Mattermost ${title}`}
    +
    + {licensee} +
    +
    {'Version:'}
    +
    {config.Version}
    +
    {'Build Number:'}
    {config.BuildNumber}
    diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 32b2e9bb7d..0f85c238da 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -22,6 +22,7 @@ import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx'; import TeamUsersTab from './team_users.jsx'; import TeamAnalyticsTab from './team_analytics.jsx'; import LdapSettingsTab from './ldap_settings.jsx'; +import LicenseSettingsTab from './license_settings.jsx'; export default class AdminController extends React.Component { constructor(props) { @@ -154,6 +155,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'ldap_settings') { tab = ; + } else if (this.state.selected === 'license') { + tab = ; } else if (this.state.selected === 'team_users') { if (this.state.teams) { tab = ; diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 1279f4d22e..5a5eaa055d 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -155,6 +155,36 @@ export default class AdminSidebar extends React.Component { } } + let ldapSettings; + let licenseSettings; + if (global.window.mm_config.BuildEnterpriseReady === 'true') { + if (global.window.mm_license.IsLicensed === 'true') { + ldapSettings = ( +
  • + + {'LDAP Settings'} + +
  • + ); + } + + licenseSettings = ( +
  • + + {'Edition and License'} + +
  • + ); + } + return (
    @@ -252,6 +282,7 @@ export default class AdminSidebar extends React.Component { {'GitLab Settings'} + {ldapSettings}
    • + {licenseSettings}
    • + const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true'; + + let bannerContent; + if (licenseEnabled) { + bannerContent = (

      {'Note:'}

      {'If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'}

      + ); + } else { + bannerContent = ( +
      + ); + } + + return ( +
      + {bannerContent}

      {'LDAP Settings'}

      {'true'} diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx new file mode 100644 index 0000000000..d490566017 --- /dev/null +++ b/web/react/components/admin_console/license_settings.jsx @@ -0,0 +1,232 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../../utils/utils.jsx'; +import * as Client from '../../utils/client.jsx'; + +export default class LicenseSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleRemove = this.handleRemove.bind(this); + + this.state = { + fileSelected: false, + serverError: null + }; + } + + handleChange() { + const element = $(ReactDOM.findDOMNode(this.refs.fileInput)); + if (element.prop('files').length > 0) { + this.setState({fileSelected: true}); + } + } + + handleSubmit(e) { + e.preventDefault(); + + const element = $(ReactDOM.findDOMNode(this.refs.fileInput)); + if (element.prop('files').length === 0) { + return; + } + const file = element.prop('files')[0]; + + $('#upload-button').button('loading'); + + const formData = new FormData(); + formData.append('license', file, file.name); + + Client.uploadLicenseFile(formData, + () => { + Utils.clearFileInput(element[0]); + $('#upload-button').button('reset'); + window.location.reload(true); + }, + (serverError) => { + this.setState({serverError}); + } + ); + } + + handleRemove(e) { + e.preventDefault(); + + $('#remove-button').button('loading'); + + Client.removeLicenseFile( + () => { + $('#remove-button').button('reset'); + window.location.reload(true); + }, + (serverError) => { + $('#remove-button').button('reset'); + this.setState({serverError}); + } + ); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
      ; + } + + var btnClass = 'btn'; + if (this.state.fileSelected) { + btnClass = 'btn btn-primary'; + } + + let edition; + let licenseType; + let licenseKey; + + if (global.window.mm_license.IsLicensed === 'true') { + edition = 'Mattermost Enterprise Edition. Designed for enterprise-scale communication.'; + licenseType = ( +
      +

      + {'This compiled release of Mattermost platform is provided under a '} + + {'commercial license'} + + {' from Mattermost, Inc. based on your subscription level and is subject to the '} + + {'Terms of Service.'} + +

      +

      {'Your subscription details are as follows:'}

      + {'Name: ' + global.window.mm_license.Name} +
      + {'Company or organization name: ' + global.window.mm_license.Company} +
      + {'Number of users: ' + global.window.mm_license.Users} +
      + {`License issued: ${Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10))} ${Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true)}`} +
      + {'Start date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10))} +
      + {'Expiry date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10))} +
      + {'LDAP: ' + global.window.mm_license.LDAP} +
      +
      + ); + + licenseKey = ( +
      + +
      +
      +

      + {'If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, '} + + {'disable all Enterprise Edition features on this server'} + + {'. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.'} +

      +
      + ); + } else { + edition = 'Mattermost Team Edition. Designed for teams from 5 to 50 users.'; + + licenseType = ( + +

      {'This compiled release of Mattermost platform is offered under an MIT license.'}

      +

      {'See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.'}

      +
      + ); + + licenseKey = ( +
      + + {serverError} + +
      +
      +

      + {'Upload a license key for Mattermost Enterprise Edition to upgrade this server. '} + + {'Visit us online'} + + {' to learn more about the benefits of Enterprise Edition or to purchase a key.'} +

      +
      + ); + } + + return ( +
      +

      {'Edition and License'}

      + +
      + +
      + {edition} +
      +
      +
      + +
      + {licenseType} +
      +
      +
      + + {licenseKey} +
      + +
      + ); + } +} diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 6337afabc4..fef253c523 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as client from '../utils/client.jsx'; +import * as Client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import * as Utils from '../utils/utils.jsx'; @@ -26,7 +26,7 @@ export default class FileUpload extends React.Component { for (var j = 0; j < data.client_ids.length; j++) { delete requests[data.client_ids[j]]; } - this.setState({requests: requests}); + this.setState({requests}); } fileUploadFail(clientId, err) { @@ -52,7 +52,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - let clientId = Utils.generateId(); + const clientId = Utils.generateId(); // prepare data to be uploaded var formData = new FormData(); @@ -60,14 +60,14 @@ export default class FileUpload extends React.Component { formData.append('files', files[i], files[i].name); formData.append('client_ids', clientId); - var request = client.uploadFile(formData, + var request = Client.uploadFile(formData, this.fileUploadSuccess.bind(this, channelId), this.fileUploadFail.bind(this, clientId) ); var requests = this.state.requests; requests[clientId] = request; - this.setState({requests: requests}); + this.setState({requests}); this.props.onUploadStart([clientId], channelId); @@ -90,16 +90,7 @@ export default class FileUpload extends React.Component { this.uploadFiles(element.prop('files')); - // clear file input for all modern browsers - try { - element[0].value = ''; - if (element.value) { - element[0].type = 'text'; - element[0].type = 'file'; - } - } catch (e) { - // Do nothing - } + Utils.clearFileInput(element[0]); } handleDrop(e) { @@ -227,14 +218,14 @@ export default class FileUpload extends React.Component { formData.append('files', file, name); formData.append('client_ids', clientId); - var request = client.uploadFile(formData, + var request = Client.uploadFile(formData, self.fileUploadSuccess.bind(self, channelId), self.fileUploadFail.bind(self, clientId) ); var requests = self.state.requests; requests[clientId] = request; - self.setState({requests: requests}); + self.setState({requests}); self.props.onUploadStart([clientId], channelId); } @@ -263,7 +254,7 @@ export default class FileUpload extends React.Component { request.abort(); delete requests[clientId]; - this.setState({requests: requests}); + this.setState({requests}); } } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 96d1ef720f..d60fea8721 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -1392,3 +1392,38 @@ export function regenOutgoingHookToken(data, success, error) { } }); } + +export function uploadLicenseFile(formData, success, error) { + $.ajax({ + url: '/api/v1/license/add', + type: 'POST', + data: formData, + cache: false, + contentType: false, + processData: false, + success, + error: function onError(xhr, status, err) { + var e = handleError('uploadLicenseFile', xhr, status, err); + error(e); + } + }); + + track('api', 'api_license_upload'); +} + +export function removeLicenseFile(success, error) { + $.ajax({ + url: '/api/v1/license/remove', + type: 'POST', + cache: false, + contentType: false, + processData: false, + success, + error: function onError(xhr, status, err) { + var e = handleError('removeLicenseFile', xhr, status, err); + error(e); + } + }); + + track('api', 'api_license_upload'); +} diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 31bb2ba0bb..24042321f8 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -201,11 +201,21 @@ export function displayDate(ticks) { return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear(); } -export function displayTime(ticks) { +export function displayTime(ticks, utc) { const d = new Date(ticks); - let hours = d.getHours(); - let minutes = d.getMinutes(); + let hours; + let minutes; let ampm = ''; + let timezone = ''; + + if (utc) { + hours = d.getUTCHours(); + minutes = d.getUTCMinutes(); + timezone = ' UTC'; + } else { + hours = d.getHours(); + minutes = d.getMinutes(); + } if (minutes <= 9) { minutes = '0' + minutes; @@ -224,7 +234,7 @@ export function displayTime(ticks) { } } - return hours + ':' + minutes + ampm; + return hours + ':' + minutes + ampm + timezone; } export function displayDateTime(ticks) { @@ -1301,3 +1311,16 @@ export function fillArray(value, length) { export function isFileTransfer(files) { return files.types != null && (files.types.indexOf ? files.types.indexOf('Files') !== -1 : files.types.contains('application/x-moz-file')); } + +export function clearFileInput(elm) { + // clear file input for all modern browsers + try { + elm.value = ''; + if (elm.value) { + elm.type = 'text'; + elm.type = 'file'; + } + } catch (e) { + // Do nothing + } +} diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss index abba9de023..b28c7d9845 100644 --- a/web/sass-files/sass/partials/_admin-console.scss +++ b/web/sass-files/sass/partials/_admin-console.scss @@ -174,6 +174,9 @@ .banner__content { width: 80%; } + &.warning { + background: #e60000; + } } .popover { border-radius: 3px; @@ -223,4 +226,4 @@ } } } -} \ No newline at end of file +} diff --git a/web/templates/head.html b/web/templates/head.html index 08d8726ea4..689c69d3ca 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -46,6 +46,7 @@