summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--README.md39
-rw-r--r--api/context.go8
-rw-r--r--model/client.go2
-rw-r--r--store/sql_channel_store.go5
-rw-r--r--store/store.go4
-rw-r--r--web/react/components/channel_header.jsx2
-rw-r--r--web/react/components/channel_info_modal.jsx2
-rw-r--r--web/react/components/channel_invite_modal.jsx2
-rw-r--r--web/react/components/channel_members.jsx2
-rw-r--r--web/react/components/channel_notifications.jsx2
-rw-r--r--web/react/components/post_list.jsx13
-rw-r--r--web/react/components/removed_from_channel_modal.jsx20
-rw-r--r--web/react/components/view_image.jsx32
-rw-r--r--web/sass-files/sass/partials/_headers.scss3
-rw-r--r--web/sass-files/sass/partials/_modal.scss7
-rw-r--r--web/sass-files/sass/partials/_popover.scss8
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss13
18 files changed, 135 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore
index 97de7eea5..79761adac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,7 @@ web/sass-files/sass/.sass-cache/
# Default local file storage
data/*
api/data/*
+
+.agignore
+.ctags
+tags
diff --git a/README.md b/README.md
index 9e49cb8b0..c4276ed78 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,45 @@ There are a few configuration settings you might want to adjust when setting up
* *ServiceSettings*:*StorageDirectory* - The file path where files will be stored locally if *UseLocalStorage* is set to true. The operating system user that is running the Mattermost application must have read and write privileges to this directory.
* *AWSSettings*:*S3*\* - If *UseLocalStorage* is set to false, and the S3 settings are configured here, then Mattermost will store files in the provided S3 bucket.
+Email Setup (Optional)
+----------------------
+
+1. Setup an email sending service. If you already have credentials for a SMTP server you can skip this step.
+ 1. [Setup Amazon Simple Email Service](https://console.aws.amazon.com/ses)
+ 2. From the `SMTP Settings` menu click `Create My SMTP Credentials`
+ 3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password`
+ 4. From the `Domains` menu setup and verify a new domain. It it also a good practice to enable `Generate DKIM Settings` for this domain.
+ 5. Choose an email address like `feedback@example.com` for Mattermost to send emails from.
+ 6. Test sending an email from `feedback@example.com` by clicking the `Send a Test Email` button and verify everything appears to be working correctly.
+2. Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information.
+ 1. If you're running Mattermost on Amazon Beanstalk you can shell into the instance with the following commands
+ 2. `ssh ec2-user@[domain for the docker instance]`
+ 3. `sudo gpasswd -a ec2-user docker`
+ 4. Retrieve the name of the container with `sudo docker ps`
+ 5. `sudo docker exec -ti container_name /bin/bash`
+2. Edit the config file `vi /config_docker.json` with the settings you captured from the step above. See an example below and notice `ByPassEmail` has been set to `false`
+
+``` bash
+"EmailSettings": {
+ "ByPassEmail" : false,
+ "SMTPUsername": "AKIADTOVBGERKLCBV",
+ "SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY",
+ "SMTPServer": "email-smtp.us-east-1.amazonaws.com:465",
+ "UseTLS": true,
+ "FeedbackEmail": "feedback@example.com",
+ "FeedbackName": "Feedback",
+ "ApplePushServer": "",
+ "ApplePushCertPublic": "",
+ "ApplePushCertPrivate": ""
+}
+```
+
+3. Restart Mattermost
+ 1. Find the process id with `ps -A` and look for the process named `platform`
+ 2. Kill the process `kill pid`
+ 3. The service should restart automatically. Verify the Mattermost service is running with `ps -A`
+ 4. Current logged in users will not be affected, but upon logging out or session expiration users will be required to verify their email address.
+
Upgrading Mattermost
---------------------
diff --git a/api/context.go b/api/context.go
index e3f279e90..8babf85f2 100644
--- a/api/context.go
+++ b/api/context.go
@@ -196,7 +196,9 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (c *Context) LogAudit(extraInfo string) {
audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId}
- Srv.Store.Audit().Save(audit)
+ if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
+ c.LogError(r.Err)
+ }
}
func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
@@ -206,7 +208,9 @@ func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
}
audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId}
- Srv.Store.Audit().Save(audit)
+ if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
+ c.LogError(r.Err)
+ }
}
func (c *Context) LogError(err *model.AppError) {
diff --git a/model/client.go b/model/client.go
index 6fcfa5043..17e2466df 100644
--- a/model/client.go
+++ b/model/client.go
@@ -5,6 +5,7 @@ package model
import (
"bytes"
+ l4g "code.google.com/p/log4go"
"fmt"
"io/ioutil"
"net/http"
@@ -93,6 +94,7 @@ func getCookie(name string, resp *http.Response) *http.Cookie {
func (c *Client) Must(result *Result, err *AppError) *Result {
if err != nil {
+ l4g.Close()
time.Sleep(time.Second)
panic(err)
}
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index cf34f2847..a7577e645 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -4,6 +4,7 @@
package store
import (
+ "fmt"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
@@ -153,8 +154,10 @@ func (s SqlChannelStore) extraUpdated(channel *model.Channel) StoreChannel {
channel.ExtraUpdated()
- if count, err := s.GetMaster().Update(channel); err != nil || count != 1 {
+ if count, err := s.GetMaster().Update(channel); err != nil {
result.Err = model.NewAppError("SqlChannelStore.extraUpdated", "Problem updating members last updated time", "id="+channel.Id+", "+err.Error())
+ } else if count != 1 {
+ result.Err = model.NewAppError("SqlChannelStore.extraUpdated", "Problem updating members last updated time", fmt.Sprintf("id=%v, count=%v", channel.Id, count))
}
storeChannel <- result
diff --git a/store/store.go b/store/store.go
index 617ea7f2b..8dbf12b55 100644
--- a/store/store.go
+++ b/store/store.go
@@ -4,7 +4,9 @@
package store
import (
+ l4g "code.google.com/p/log4go"
"github.com/mattermost/platform/model"
+ "time"
)
type StoreResult struct {
@@ -17,6 +19,8 @@ type StoreChannel chan StoreResult
func Must(sc StoreChannel) interface{} {
r := <-sc
if r.Err != nil {
+ l4g.Close()
+ time.Sleep(time.Second)
panic(r.Err)
}
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 90a776791..0254d0e82 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -105,7 +105,7 @@ module.exports = React.createClass({
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
+ $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}});
},
onSocketChange: function(msg) {
if (msg.action === 'new_user') {
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index 18addb52f..6d999870a 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -32,7 +32,7 @@ module.exports = React.createClass({
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">{channel.display_name}</h4>
+ <h4 className="modal-title" id="myModalLabel"><span className="name">{channel.display_name}</span></h4>
</div>
<div className="modal-body">
<div className="row form-group">
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index d90522e8c..e446167ec 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -138,7 +138,7 @@ export default class ChannelInviteModal extends React.Component {
<div className='modal-content'>
<div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title'>Add New Members to {this.state.channelName}</h4>
+ <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channel_name}</span></h4>
</div>
<div className='modal-body'>
{inviteError}
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
index cfb8ed41c..db4bec400 100644
--- a/web/react/components/channel_members.jsx
+++ b/web/react/components/channel_members.jsx
@@ -126,7 +126,7 @@ module.exports = React.createClass({
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
- <h4 className="modal-title">{this.state.channel_name + " Members"}</h4>
+ <h4 className="modal-title"><span className="name">{this.state.channel_name}</span> Members</h4>
<a className="btn btn-md btn-primary" data-toggle="modal" data-target="#channel_invite"><i className="glyphicon glyphicon-envelope"/> Add New Members</a>
</div>
<div ref="modalBody" className="modal-body">
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index cd477feb7..884bad9d2 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -300,7 +300,7 @@ export default class ChannelNotifications extends React.Component {
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Close</span>
</button>
- <h4 className='modal-title'>{'Notification Preferences for ' + this.state.title}</h4>
+ <h4 className='modal-title'>Notification Preferences for <span className='name'>{this.state.title}</span></h4>
</div>
<div className='modal-body'>
<div className='settings-table'>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 8b60f0251..7748f5c2a 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -75,6 +75,7 @@ module.exports = React.createClass({
utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
+ utils.changeCss('.nav-pills__unread-indicator', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
}
if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
@@ -377,8 +378,8 @@ module.exports = React.createClass({
<strong><UserProfile userId={teammate.id} /></strong>
</div>
<p className='channel-intro-text'>
- {'This is the start of your private message history with ' + teammateName + '.'}<br/>
- {'Private messages and files shared here are not shown to people outside this area.'}
+ This is the start of your private message history with <strong>{teammateName}</strong>.<br/>
+ Private messages and files shared here are not shown to people outside this area.
</p>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className='fa fa-pencil'></i>Set a description</a>
</div>
@@ -416,9 +417,9 @@ module.exports = React.createClass({
<div className='channel-intro'>
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
<p className='channel-intro__content'>
- Welcome to {uiName}!
+ Welcome to <strong>{uiName}</strong>!
<br/><br/>
- {'This is the first channel ' + strings.Team + 'mates see when they'}
+ This is the first channel {strings.Team}mates see when they
<br/>
sign up - use it for posting updates everyone needs to know.
<br/><br/>
@@ -434,7 +435,7 @@ module.exports = React.createClass({
<div className='channel-intro'>
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
<p className='channel-intro__content'>
- {'This is the start of ' + uiName + ', a channel for non-work-related conversations.'}
+ This is the start of <strong>{uiName}</strong>, a channel for non-work-related conversations.
<br/>
</p>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={uiName} data-channelid={channel.id}><i className='fa fa-pencil'></i>Set a description</a>
@@ -453,7 +454,7 @@ module.exports = React.createClass({
var createMessage;
if (creatorName !== '') {
- createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created by ' + creatorName + ' on ' + utils.displayDate(channel.create_at) + '.';
+ createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>);
} else {
createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.';
}
diff --git a/web/react/components/removed_from_channel_modal.jsx b/web/react/components/removed_from_channel_modal.jsx
index a8889a92a..4a49e1c98 100644
--- a/web/react/components/removed_from_channel_modal.jsx
+++ b/web/react/components/removed_from_channel_modal.jsx
@@ -20,7 +20,7 @@ module.exports = React.createClass({
var townSquare = ChannelStore.getByName("town-square");
utils.switchChannel(townSquare);
- this.setState({channelName: "", remover: ""})
+ this.setState({channelName: "", remover: ""});
},
componentDidMount: function() {
$(this.getDOMNode()).on('show.bs.modal',this.handleShow);
@@ -40,18 +40,18 @@ module.exports = React.createClass({
if (currentUser != null) {
return (
- <div className="modal fade" ref="modal" id="removed_from_channel" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Removed from {channelName}</h4>
+ <div className='modal fade' ref='modal' id='removed_from_channel' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
+ <h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4>
</div>
- <div className="modal-body">
+ <div className='modal-body'>
<p>{remover} removed you from {channelName}</p>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button>
+ <div className='modal-footer'>
+ <button type='button' className='btn btn-primary' data-dismiss='modal'>Okay</button>
</div>
</div>
</div>
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 2b7f64030..6077c4ebc 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -109,6 +109,26 @@ module.exports = React.createClass({
}
);
+ if (this.refs.previewArrowLeft) {
+ $(this.refs.previewArrowLeft.getDOMNode()).hover(
+ function onModalHover() {
+ $(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
+ }, function offModalHover() {
+ $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
+ }
+ );
+ }
+
+ if (this.refs.previewArrowRight) {
+ $(this.refs.previewArrowRight.getDOMNode()).hover(
+ function onModalHover() {
+ $(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
+ }, function offModalHover() {
+ $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
+ }
+ );
+ }
+
$(window).on('keyup', this.handleKeyPress);
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
@@ -252,13 +272,21 @@ module.exports = React.createClass({
var rightArrow = '';
if (this.props.filenames.length > 1) {
leftArrow = (
- <a className='modal-prev-bar' href='#' onClick={this.handlePrev}>
+ <a
+ ref='previewArrowLeft'
+ className='modal-prev-bar'
+ href='#'
+ onClick={this.handlePrev}>
<i className='image-control image-prev'/>
</a>
);
rightArrow = (
- <a className='modal-next-bar' href='#' onClick={this.handleNext}>
+ <a
+ ref='previewArrowRight'
+ className='modal-next-bar'
+ href='#'
+ onClick={this.handleNext}>
<i className='image-control image-next'/>
</a>
);
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index da648a170..571c7ff1f 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -65,6 +65,9 @@
}
.channel-intro-img {
float:left;
+ img {
+ @include border-radius(100px);
+ }
}
.channel-intro__title {
font-weight:600;
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 014f834ed..dec08b567 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -50,6 +50,13 @@
@include clearfix;
.modal-title {
float: left;
+ font-size: 17px;
+ line-height: 27px;
+ color: #f4f4f4;
+ .name {
+ font-weight: 600;
+ color: #fff;
+ }
}
.modal-action {
padding: 0;
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index 5008331b4..126d239ec 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -1,3 +1,8 @@
+.channel-header__info .popover-content {
+ max-height: 250px;
+ overflow: auto;
+}
+
.user-popover {
cursor: pointer;
display: inline-block;
@@ -13,4 +18,5 @@
overflow: hidden;
text-overflow: ellipsis;
display: block;
-} \ No newline at end of file
+}
+
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index 7bbaa21cb..6b827eaee 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -38,24 +38,25 @@
height: 100%;
position: relative;
overflow: auto;
-
}
.nav-pills__unread-indicator {
position: absolute;
left: 0;
right: 0;
- width: 70%;
- background-color: darken($primary-color, 5%);
- color: white;
+ width: 72%;
+ color: #777;
+ background: #DCF0FF;
+ @include border-radius(50px);
margin: 0 auto;
- padding: 2px;
+ padding: 3px 0 4px;
+ font-size: 13.5px;
text-align: center;
z-index: 1;
}
.nav-pills__unread-indicator-top {
- top: 56px;
+ top: 66px;
}
.nav-pills__unread-indicator-bottom {
bottom: 0px;