diff options
25 files changed, 198 insertions, 48 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 @@ -60,7 +60,7 @@ Local Machine Setup (Docker) 3. When docker is done fetching the image, open http://localhost:8065/ in your browser. ### Arch ### -1. Install docker using the following commands: +1. Install Docker using the following commands: ``` bash pacman -S docker @@ -70,13 +70,13 @@ Local Machine Setup (Docker) newgrp docker ``` -2. Start docker container: +2. Start Docker container: ``` bash docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform ``` -3. When docker is done fetching the image, open http://localhost:8065/ in your browser. +3. When Docker is done fetching the image, open http://localhost:8065/ in your browser. ### Additional Notes ### - If you want to work with the latest master from the repository (i.e. not a stable release) you can run the cmd: @@ -85,7 +85,7 @@ Local Machine Setup (Docker) docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev ``` -- Instructions on how to update your docker image are found below. +- Instructions on how to update your Docker image are found below. - If you wish to remove mattermost-dev use: @@ -103,7 +103,7 @@ Local Machine Setup (Docker) AWS Elastic Beanstalk Setup (Docker) ------------------------------------ -1. Create a new elastic beanstalk docker application using the [Dockerrun.aws.json](docker/0.6/Dockerrun.aws.json) file provided. +1. Create a new Elastic Beanstalk Docker application using the [Dockerrun.aws.json](docker/0.6/Dockerrun.aws.json) file provided. 1. From the AWS console select Elastic Beanstalk. 2. Select "Create New Application" from the top right. 3. Name the application and press next. @@ -125,41 +125,80 @@ AWS Elastic Beanstalk Setup (Docker) Configuration Settings ---------------------- -There are a few configuration settings you might want to adjust when setting up your instance of Mattermost. You can edit them in [config/config.json](config/config.json) or [docker/0.6/config_docker.json](docker/0.6/config_docker.json) if you're running a docker instance. +There are a few configuration settings you might want to adjust when setting up your instance of Mattermost. You can edit them in [config/config.json](config/config.json) or [docker/0.6/config_docker.json](docker/0.6/config_docker.json) if you're running a Docker instance. * *EmailSettings*:*ByPassEmail* - If this is set to true, then users on the system will not need to verify their email addresses when signing up. In addition, no emails will ever be sent. * *ServiceSettings*:*UseLocalStorage* - If this is set to true, then your Mattermost server will store uploaded files in the storage directory specified by *StorageDirectory*. *StorageDirectory* must be set if *UseLocalStorage* is set to true. * *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. -Upgrading Mattermost ---------------------- +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 Preview +---------------------------- ### Docker ### -To upgrade your docker image to the latest release (NOTE: this will destroy all data in the docker container): +To upgrade your Docker image to a preview of the latest stable release (NOTE: this will erase all data in the Docker container, including the database): -1. Stop your docker container by running: +1. Stop your Docker container by running: ``` bash docker stop mattermost-dev ``` -2. Delete your docker container by running: +2. Delete your Docker container by running: ``` bash docker rm mattermost-dev ``` -3. Update your docker image by running: +3. Update your Docker image by running: ``` bash docker pull mattermost/platform ``` -4. Start your docker container by running: +4. Start your Docker container by running: ``` bash docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform ``` -To upgrade to the latest master from the repository replace `mattermost/platform` with `mattermost/platform:dev` in the above instructions. +To upgrade to the latest development build on master from the repository replace `mattermost/platform` with `mattermost/platform:dev` in the instructions 3) and 4) above. Contributing ------------ 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/requirements.md b/requirements.md new file mode 100644 index 000000000..cc0d1833d --- /dev/null +++ b/requirements.md @@ -0,0 +1,23 @@ +# Requirements + +## Web Client + +Supported Operating Systems and Browsers: + +- PC: Windows 7, Windows 8 (Chrome 43+, Firefox 38+, Internet Explorer 10+) +- Mac: OS 10 (Safari 7, Chrome 43+) +- Linux: Arch 4.0.0 (Chrome 43+) +- iPhone 4s and higher (Safari on iOS 8.3+, Chrome 43+) +- Android 5 and higher (Chrome 43+) + +## Server + +While the pre-released version of Mattermost is not currently supported in production, the intention from the product team is to support: + +- Ubuntu +- Debian +- CentOS +- RedHat Enterprise Linux +- Oracle Linux + +The Mattermost roadmap does not currently include production support for Fedora, FreeBSD or Arch Linux. diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index cf34f2847..d503d2225 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -153,7 +153,16 @@ func (s SqlChannelStore) extraUpdated(channel *model.Channel) StoreChannel { channel.ExtraUpdated() - if count, err := s.GetMaster().Update(channel); err != nil || count != 1 { + _, err := s.GetMaster().Exec( + `UPDATE + Channels + SET + ExtraUpdateAt = :Time + WHERE + Id = :Id`, + map[string]interface{}{"Id": channel.Id, "Time": channel.ExtraUpdateAt}) + + if err != nil { result.Err = model.NewAppError("SqlChannelStore.extraUpdated", "Problem updating members last updated time", "id="+channel.Id+", "+err.Error()) } 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/utils/config.go b/utils/config.go index 536d0d802..46daf203c 100644 --- a/utils/config.go +++ b/utils/config.go @@ -234,8 +234,8 @@ func LoadConfig(fileName string) { // Check for a valid email for feedback, if not then do feedback@domain if _, err := mail.ParseAddress(config.EmailSettings.FeedbackEmail); err != nil { - config.EmailSettings.FeedbackEmail = "feedback@localhost" l4g.Error("Misconfigured feedback email setting: %s", config.EmailSettings.FeedbackEmail) + config.EmailSettings.FeedbackEmail = "feedback@localhost" } configureLog(config.LogSettings) diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index e446167ec..b70811db1 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'>×</span></button> - <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channel_name}</span></h4> + <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4> </div> <div className='modal-body'> {inviteError} diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 1a633b193..76f0c2c4d 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -50,13 +50,18 @@ module.exports = React.createClass({ render: function() { var server_error = this.state.server_error ? <div className='form-group has-error'><br/><label className='control-label'>{ this.state.server_error }</label></div> : null; + var editTitle = <h4 className='modal-title' ref='title'>Edit Description</h4>; + if (this.state.title) { + editTitle = <h4 className='modal-title' ref='title'>Edit Description for <span className='name'>{this.state.title}</span></h4>; + } + return ( <div className="modal fade" ref="modal" id="edit_channel" role="dialog" tabIndex="-1" 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">×</span></button> - <h4 className="modal-title" ref="title">Edit Description for {this.state.title}</h4> + {editTitle} </div> <div className="modal-body"> <textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea> diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index b00376758..38c9ea76d 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -127,12 +127,12 @@ module.exports = React.createClass({ <div className='modal-body'> <div className={displayNameClass}> <label className='control-label'>Display Name</label> - <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' maxLength='64' /> + <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' maxLength='22' /> {displayNameError} </div> <div className={nameClass}> <label className='control-label'>Handle</label> - <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='64' /> + <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='22' /> {nameError} </div> <div className='form-group'> diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 7bc6a8c01..53ffeb400 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -102,7 +102,7 @@ module.exports = React.createClass({ currentUserCss = "current--user"; } - var timestamp = UserStore.getCurrentUser().update_at; + var timestamp = UserStore.getProfile(post.user_id).update_at; return ( <div> diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index e39cf5d46..b11b39e9e 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -48,6 +48,13 @@ module.exports = React.createClass({ }); AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH_TERM, + term: null, + do_search: false, + is_mention_search: false + }); + + AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POST_SELECTED, results: null }); diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx index 643ad112b..b1efd7685 100644 --- a/web/react/components/search_results.jsx +++ b/web/react/components/search_results.jsx @@ -24,6 +24,13 @@ var RhsHeaderSearch = React.createClass({ }); AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH_TERM, + term: null, + do_search: false, + is_mention_search: false + }); + + AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POST_SELECTED, results: null }); diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx index e97b67706..5b12ad7e9 100644 --- a/web/react/components/setting_picture.jsx +++ b/web/react/components/setting_picture.jsx @@ -48,6 +48,7 @@ module.exports = React.createClass({ } confirmButton = <a className={confirmButtonClass} onClick={this.props.submit}>Save</a>; } + var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + config.ProfileWidth + 'px in width and ' + config.ProfileHeight + 'px height.' var self = this; return ( @@ -59,6 +60,9 @@ module.exports = React.createClass({ {img} </li> <li className='setting-list-item'> + {helpText} + </li> + <li className='setting-list-item'> {serverError} {clientError} <span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span> diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx index 83b6d85fc..02789f5dd 100644 --- a/web/react/components/setting_upload.jsx +++ b/web/react/components/setting_upload.jsx @@ -46,17 +46,25 @@ module.exports = React.createClass({ serverError: '' }); }, + onFileSelect: function(e) { + var filename = $(e.target).val(); + if (filename.substring(3, 11) === 'fakepath') { + filename = filename.substring(12); + } + $(e.target).closest('li').find('.file-status').addClass('hide'); + $(e.target).closest('li').find('.file-name').removeClass('hide').html(filename); + }, render: function() { var clientError = null; if (this.state.clientError) { clientError = ( - <div className='form-group has-error'><label className='control-label'>{this.state.clientError}</label></div> + <div className='file-status'>{this.state.clientError}</div> ); } var serverError = null; if (this.state.serverError) { serverError = ( - <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div> + <div className='file-status'>{this.state.serverError}</div> ); } return ( @@ -65,11 +73,21 @@ module.exports = React.createClass({ <li className='col-xs-offset-3 col-xs-8'> <ul className='setting-list'> <li className='setting-list-item'> + <span className='btn btn-sm btn-primary btn-file sel-btn'>Select file<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span> + <a + className={'btn btn-sm btn-primary'} + onClick={this.doSubmit}> + Import + </a> + <a + className='btn btn-sm btn-link theme' + href='#' + onClick={this.doCancel}> + Cancel + </a> + <div className='file-status file-name hide'></div> {serverError} {clientError} - <span className='btn btn-sm btn-primary btn-file sel-btn'>Select File<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span> - <a className={'btn btn-sm btn-primary'} onClick={this.doSubmit}>Import</a> - <a className='btn btn-sm theme' href='#' onClick={this.doCancel}>Cancel</a> </li> </ul> </li> diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx index ee0bfa874..4f28d84c6 100644 --- a/web/react/components/team_feature_tab.jsx +++ b/web/react/components/team_feature_tab.jsx @@ -133,10 +133,10 @@ module.exports = React.createClass({ <div> <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' ref='title'><i className='modal-back'></i>Feature Settings</h4> + <h4 className='modal-title' ref='title'><i className='modal-back'></i>Advanced Features</h4> </div> <div ref='wrapper' className='user-settings'> - <h3 className='tab-header'>Feature Settings</h3> + <h3 className='tab-header'>Advanced Features</h3> <div className='divider-dark first'/> {valetSection} <div className='divider-dark'/> diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index ae7f875cb..c21701c0e 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -34,17 +34,17 @@ module.exports = React.createClass({ break; case 'in-progress': messageSection = ( - <p>Importing...</p> + <p className="confirm-import alert alert-warning"><i className="fa fa-spinner fa-pulse"></i> Importing...</p> ); break; case 'done': messageSection = ( - <p>Import sucessfull: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p> + <p className="confirm-import alert alert-success"><i className="fa fa-check"></i> Import sucessfull: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p> ); break; case 'fail': messageSection = ( - <p>Import failure: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p> + <p className="confirm-import alert alert-warning"><i className="fa fa-warning"></i> Import failure: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p> ); break; } @@ -59,8 +59,8 @@ module.exports = React.createClass({ <h3 className='tab-header'>Import</h3> <div className='divider-dark first'/> {uploadSection} - {messageSection} <div className='divider-dark'/> + {messageSection} </div> </div> ); diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx index c9f479a22..ef2564de0 100644 --- a/web/react/components/team_settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -27,8 +27,8 @@ module.exports = React.createClass({ }, render: function() { var tabs = []; - tabs.push({name: 'feature', uiName: 'Features', icon: 'glyphicon glyphicon-wrench'}); tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'}); + tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'}); return ( <div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'> diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 103292abf..754843697 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -252,6 +252,7 @@ module.exports.revokeSession = function(altId, success, error) { module.exports.getSessions = function(userId, success, error) { $.ajax({ + cache: false, url: '/api/v1/users/' + userId + '/sessions', dataType: 'json', contentType: 'application/json', @@ -282,6 +283,7 @@ module.exports.getMeSynchronous = function(success, error) { var currentUser = null; $.ajax({ async: false, + cache: false, url: '/api/v1/users/me', dataType: 'json', contentType: 'application/json', @@ -293,12 +295,9 @@ module.exports.getMeSynchronous = function(success, error) { } }, error: function onError(xhr, status, err) { - var ieChecker = window.navigator.userAgent; // This and the condition below is used to check specifically for browsers IE10 & 11 to suppress a 200 'OK' error from appearing on login - if (xhr.status !== 200 || !(ieChecker.indexOf('Trident/7.0') > 0 || ieChecker.indexOf('Trident/6.0') > 0)) { - if (error) { - var e = handleError('getMeSynchronous', xhr, status, err); - error(e); - } + if (error) { + var e = handleError('getMeSynchronous', xhr, status, err); + error(e); } } }); @@ -566,6 +565,7 @@ module.exports.updateLastViewedAt = function(channelId, success, error) { function getChannels(success, error) { $.ajax({ + cache: false, url: '/api/v1/channels/', dataType: 'json', type: 'GET', @@ -581,6 +581,7 @@ module.exports.getChannels = getChannels; module.exports.getChannel = function(id, success, error) { $.ajax({ + cache: false, url: '/api/v1/channels/' + id + '/', dataType: 'json', type: 'GET', @@ -610,6 +611,7 @@ module.exports.getMoreChannels = function(success, error) { function getChannelCounts(success, error) { $.ajax({ + cache: false, url: '/api/v1/channels/counts', dataType: 'json', type: 'GET', @@ -653,6 +655,7 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e module.exports.getPosts = function(channelId, offset, limit, success, error, complete) { $.ajax({ + cache: false, url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit, dataType: 'json', type: 'GET', @@ -668,6 +671,7 @@ module.exports.getPosts = function(channelId, offset, limit, success, error, com module.exports.getPost = function(channelId, postId, success, error) { $.ajax({ + cache: false, url: '/api/v1/channels/' + channelId + '/post/' + postId, dataType: 'json', type: 'GET', @@ -791,6 +795,7 @@ module.exports.removeChannelMember = function(id, data, success, error) { module.exports.getProfiles = function(success, error) { $.ajax({ + cache: false, url: '/api/v1/users/profiles', dataType: 'json', contentType: 'application/json', diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 09cd299df..4571312bb 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -271,11 +271,12 @@ function getYoutubeEmbed(link) { iframe.setAttribute('src', 'https://www.youtube.com/embed/' + div.id + - '?autoplay=1&autohide=1&border=0&wmode=opaque&enablejsapi=1'); + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1'); iframe.setAttribute('width', '480px'); iframe.setAttribute('height', '360px'); iframe.setAttribute('type', 'text/html'); iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', 'allowfullscreen'); div.parentNode.replaceChild(iframe, div); } diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index 571c7ff1f..c311941b6 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -200,6 +200,9 @@ } } } + .search__clear { + display: none; + } } #navbar { diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 0262ef60c..99a7eb7bc 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -111,6 +111,16 @@ } } + .file-status { + font-size: 13px; + margin-top: 8px; + color: #555; + } + + .confirm-import { + padding: 4px 10px; + margin: 10px 0; + } } } diff --git a/web/templates/head.html b/web/templates/head.html index 5448b09ed..02904e764 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -50,6 +50,8 @@ config = {}; } config.SiteName = '{{ .SiteName }}'; + config.ProfileWidth = '{{ .Props.ProfileWidth }}' + config.ProfileHeight = '{{ .Props.ProfileHeight }}' </script> <script src="/static/js/bundle.js"></script> diff --git a/web/web.go b/web/web.go index d6f8d553b..dc2b5dced 100644 --- a/web/web.go +++ b/web/web.go @@ -4,18 +4,19 @@ package web import ( - l4g "code.google.com/p/log4go" "fmt" + "html/template" + "net/http" + "strconv" + "strings" + + l4g "code.google.com/p/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/api" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" "github.com/mssola/user_agent" "gopkg.in/fsnotify.v1" - "html/template" - "net/http" - "strconv" - "strings" ) var Templates *template.Template @@ -30,6 +31,8 @@ func NewHtmlTemplatePage(templateName string, title string) *HtmlTemplatePage { props := make(map[string]string) props["AnalyticsUrl"] = utils.Cfg.ServiceSettings.AnalyticsUrl + props["ProfileHeight"] = fmt.Sprintf("%v", utils.Cfg.ImageSettings.ProfileHeight) + props["ProfileWidth"] = fmt.Sprintf("%v", utils.Cfg.ImageSettings.ProfileWidth) return &HtmlTemplatePage{TemplateName: templateName, Title: title, SiteName: utils.Cfg.ServiceSettings.SiteName, Props: props} } @@ -212,7 +215,7 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request) props := model.MapFromJson(strings.NewReader(data)) t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour + if err != nil || model.GetMillis()-t > 1000*60*60*24*30 { // 30 days c.Err = model.NewAppError("signupTeamComplete", "The signup link has expired", "") return } |