summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAsaad Mahmood <Unknowngi@live.com>2015-09-30 19:30:45 +0500
committerAsaad Mahmood <Unknowngi@live.com>2015-09-30 19:30:45 +0500
commit296b93076888c9b26329938bd1cf1587397c7f51 (patch)
treea37a396752971c88e13fc97d88531357ca09542e
parentfb285d7b32a6c5e559277bcb32b3b7ff6889af22 (diff)
parent13ebb5fe94bfee4e529f634f02d285fa7d8c0dfc (diff)
downloadchat-296b93076888c9b26329938bd1cf1587397c7f51.tar.gz
chat-296b93076888c9b26329938bd1cf1587397c7f51.tar.bz2
chat-296b93076888c9b26329938bd1cf1587397c7f51.zip
Merge branch 'master' of https://github.com/mattermost/platform into plt-383
-rw-r--r--README.md4
-rw-r--r--api/admin.go11
-rw-r--r--api/command.go2
-rw-r--r--api/command_test.go5
-rw-r--r--api/team.go5
-rw-r--r--api/user.go7
-rw-r--r--config/config.json2
-rw-r--r--doc/install/dev-setup.md11
-rw-r--r--doc/install/prod-ubuntu.md187
-rw-r--r--doc/integrations/webhook/incoming.md5
-rw-r--r--docker/0.5/Dockerfile100
-rwxr-xr-xdocker/0.5/Dockerrun.aws.json13
-rwxr-xr-xdocker/0.5/docker-entry.sh122
-rw-r--r--docker/0.5/main.cf28
-rw-r--r--docker/0.6/Dockerrun.aws.zipbin867 -> 0 bytes
-rw-r--r--docker/0.6/config_docker.json99
-rw-r--r--docker/1.0/Dockerfile (renamed from docker/0.6/Dockerfile)8
-rw-r--r--docker/1.0/Dockerrun.aws.zipbin0 -> 711 bytes
-rw-r--r--docker/1.0/Dockerrun.aws/.ebextensions/01_files.config (renamed from docker/0.6/Dockerrun.aws/.ebextensions/01_files.config)0
-rwxr-xr-xdocker/1.0/Dockerrun.aws/Dockerrun.aws.json (renamed from docker/0.6/Dockerrun.aws/Dockerrun.aws.json)2
-rw-r--r--docker/1.0/config_docker.json89
-rwxr-xr-xdocker/1.0/docker-entry.sh (renamed from docker/0.6/docker-entry.sh)0
-rw-r--r--docker/dev/config_docker.json4
-rw-r--r--docker/local/config_docker.json4
-rw-r--r--model/config.go92
-rw-r--r--model/version.go2
-rw-r--r--model/version_test.go2
-rw-r--r--store/sql_channel_store.go4
-rw-r--r--store/sql_post_store.go4
-rw-r--r--store/sql_post_store_test.go7
-rw-r--r--store/sql_store.go22
-rw-r--r--utils/config.go5
-rw-r--r--utils/mail.go23
-rw-r--r--web/react/components/admin_console/email_settings.jsx4
-rw-r--r--web/react/components/admin_console/gitlab_settings.jsx105
-rw-r--r--web/react/components/command_list.jsx4
-rw-r--r--web/react/components/more_direct_channels.jsx2
-rw-r--r--web/react/components/navbar_dropdown.jsx2
-rw-r--r--web/react/components/rename_channel_modal.jsx1
-rw-r--r--web/react/components/search_results_item.jsx7
-rw-r--r--web/react/components/sidebar.jsx15
-rw-r--r--web/react/components/sidebar_right.jsx4
-rw-r--r--web/react/components/sidebar_right_menu.jsx15
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx16
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx2
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/utils.jsx41
-rw-r--r--web/sass-files/sass/partials/_command-box.scss17
-rw-r--r--web/sass-files/sass/partials/_responsive.scss8
-rw-r--r--web/sass-files/sass/partials/_search.scss3
-rw-r--r--web/web.go7
51 files changed, 589 insertions, 534 deletions
diff --git a/README.md b/README.md
index 58f4887f6..88f6dcb35 100644
--- a/README.md
+++ b/README.md
@@ -52,10 +52,12 @@ There are multiple ways to install Mattermost depending on your needs.
[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform)
-#### Production Deployment (for Beta2 and later)
+#### Production Deployment
Prior to production installation, please review [Mattermost system requirements](doc/install/requirements.md).
+- [Production Install on Ubuntu 14.04](https://github.com/mattermost/platform/blob/release-1.0.0/doc/install/prod-ubuntu.md) - Install Mattermost for production environments.
+
- [GitLab Mattermost Production Installation](https://gitlab.com/gitlab-org/gitlab-mattermost) - Install Mattermost for production environments bundled with GitLab, a leading open source Git repository, using an omnibus package for Ubuntu 12.04, Ubuntu 14.04, Debian 7, Debian 8, and CentOS 6 (and RedHat/Oracle/Scientific Linux 6), CentOS 7 (and RedHat/Oracle/Scientific Linux 7).
For technical questions and answers, please visit the [Mattermost forum](http://forum.mattermost.org).
diff --git a/api/admin.go b/api/admin.go
index 568d8f6e8..3ef8c12a8 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -82,18 +82,11 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if len(cfg.ServiceSettings.ListenAddress) == 0 {
- c.SetInvalidParam("saveConfig", "config")
- return
- }
-
- if cfg.TeamSettings.MaxUsersPerTeam == 0 {
- c.SetInvalidParam("saveConfig", "config")
+ if err := cfg.IsValid(); err != nil {
+ c.Err = err
return
}
- // TODO run some cleanup validators
-
utils.SaveConfig(utils.CfgFileName, cfg)
utils.LoadConfig(utils.CfgFileName)
json := utils.Cfg.ToJson()
diff --git a/api/command.go b/api/command.go
index 0d2f7597b..427922344 100644
--- a/api/command.go
+++ b/api/command.go
@@ -195,7 +195,7 @@ func joinCommand(c *Context, command *model.Command) bool {
return false
}
- command.GotoLocation = "/channels/" + v.Name
+ command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name
command.Response = model.RESP_EXECUTED
return true
}
diff --git a/api/command_test.go b/api/command_test.go
index fe52dd41b..d70729448 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -4,6 +4,7 @@
package api
import (
+ "strings"
"testing"
"github.com/mattermost/platform/model"
@@ -126,12 +127,12 @@ func TestJoinCommands(t *testing.T) {
}
rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command)
- if rs5.GotoLocation != "/channels/"+channel2.Name {
+ if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) {
t.Fatal("failed to join channel")
}
rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command)
- if rs6.GotoLocation == "/channels/"+channel3.Name {
+ if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) {
t.Fatal("should not have joined direct message channel")
}
diff --git a/api/team.go b/api/team.go
index 4794b66df..fba4b41c6 100644
--- a/api/team.go
+++ b/api/team.go
@@ -75,7 +75,10 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- m["follow_link"] = bodyPage.Props["Link"]
+ if !utils.Cfg.EmailSettings.RequireEmailVerification {
+ m["follow_link"] = bodyPage.Props["Link"]
+ }
+
w.Header().Set("Access-Control-Allow-Origin", " *")
w.Write([]byte(model.MapToJson(m)))
}
diff --git a/api/user.go b/api/user.go
index 4240a795e..e140bc8cf 100644
--- a/api/user.go
+++ b/api/user.go
@@ -198,7 +198,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err)
}
- fireAndForgetWelcomeEmail(ruser.Email, team.DisplayName, c.GetTeamURLFromTeam(team))
+ fireAndForgetWelcomeEmail(ruser.Email, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
if user.EmailVerified {
if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
l4g.Error("Failed to set email verified err=%v", cresult.Err)
@@ -218,12 +218,13 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
}
}
-func fireAndForgetWelcomeEmail(email, teamDisplayName, teamURL string) {
+func fireAndForgetWelcomeEmail(email, teamDisplayName, siteURL, teamURL string) {
go func() {
subjectPage := NewServerTemplatePage("welcome_subject")
subjectPage.Props["TeamDisplayName"] = teamDisplayName
bodyPage := NewServerTemplatePage("welcome_body")
+ bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["TeamURL"] = teamURL
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
@@ -997,7 +998,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
sessions := result.Data.([]*model.Session)
for _, s := range sessions {
- sessionCache.Remove(s.Id)
+ sessionCache.Remove(s.Token)
}
}
diff --git a/config/config.json b/config/config.json
index 60e197154..38acee85a 100644
--- a/config/config.json
+++ b/config/config.json
@@ -5,7 +5,7 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": false,
+ "EnableIncomingWebhooks": true,
"EnableTesting": false
},
"TeamSettings": {
diff --git a/doc/install/dev-setup.md b/doc/install/dev-setup.md
index b90b6a351..c63bde512 100644
--- a/doc/install/dev-setup.md
+++ b/doc/install/dev-setup.md
@@ -3,13 +3,14 @@ Developer Machine Setup
### Mac OS X ###
-1. Download and set up Boot2Docker
+1. Download and set up Docker Toolbox
1. Follow the instructions at http://docs.docker.com/installation/mac/
- 1. Use the Boot2Docker command-line utility
- 2. If you do command-line setup use: `boot2docker init eval “$(boot2docker shellinit)”`
- 2. Get your Docker IP address with `boot2docker ip`
+ 2. Start a new docker host
+ `docker-machine create -d virtualbox dev`
+ 2. Get the IP address of your docker host
+ `docker-machine ip dev`
3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost`
- 4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile
+ 4. Run `docker-machine env dev` and copy the export statements to your ~/.bash_profile
2. Download Go (version 1.4.2) from http://golang.org/dl/
3. Set up your Go workspace
1. `mkdir ~/go`
diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md
new file mode 100644
index 000000000..866b1bdbe
--- /dev/null
+++ b/doc/install/prod-ubuntu.md
@@ -0,0 +1,187 @@
+# Production Installation on Ubuntu 14.04 LTS
+
+## Install Ubuntu Server 14.04 LTS
+1. Set up 3 machines with Ubuntu 14.04 with 2GB of RAM or more. The servers will be used for the Load Balancer, Mattermost, and Database.
+1. Make sure the system is up to date with the most recent security patches.
+ * ``` sudo apt-get update```
+ * ``` sudo apt-get upgrade```
+
+## Setup Database Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1
+1. Install PostgreSQL 9.3+ (or MySQL 5.2+)
+ * ``` sudo apt-get install postgresql postgresql-contrib```
+1. PostgreSQL created a user account called `postgres`. You will need to log into that account with:
+ * ``` sudo -i -u postgres```
+1. You can get a PostgreSQL prompt by typing:
+ * ``` psql```
+1. Create the Mattermost database by typing:
+ * ```postgres=# CREATE DATABASE mattermost;```
+1. Create the Mattermost user by typing:
+ * ```postgres=# CREATE USER mmuser WITH PASSWORD 'mmuser_password';```
+1. Grant the user access to the Mattermost database by typing:
+ * ```postgres=# GRANT ALL PRIVILEGES ON DATABASE mattermost to mmuser;```
+1. You can exit out of PostgreSQL by typing:
+ * ```postgre=# \q```
+1. You can exit the postgres account by typing:
+ * ``` exit```
+
+## Setup Mattermost Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2
+1. Download the latest Mattermost Server by typing:
+ * ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz```
+1. Unzip the Mattermost Server by typing:
+ * ``` tar -xvzf mattermost.tar.gz```
+1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`, in the future we will give guidance for storing under `/opt`.
+1. We have also elected to run the Mattermost Server as the `ubuntu` account for simplicity. We recommend settings up and running the service under a `mattermost` user account with limited permissions.
+1. Create the storage directory for files. We assume you will have attached a large drive for storage of images and files. For this setup we will assume the directory is located at `/mattermost/data`.
+ * Create the directory by typing:
+ * ``` sudo mkdir -p /mattermost/data```
+ * Set the ubuntu account as the directory owner by typing:
+ * ``` sudo chown -R ubuntu /mattermost```
+1. Configure Mattermost Server by editing the config.json file at /home/ubuntu/mattermost/config`
+ * ``` cd ~/mattermost/config```
+ * Edit the file by typing:
+ * ``` vi config.json```
+ * replace `DriverName": "mysql"` with `DriverName": "postgres"`
+ * replace `"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8"` with `"DataSource": "postgres://mmuser:mmuser_password@10.10.10.1:5432/mattermost?sslmode=disable&connect_timeout=10"`
+ * Optionally you may continue to edit configuration settings in `config.json` or use the System Console described in a later section to finish the configuration.
+1. Test the Mattermost Server
+ * ``` cd ~/mattermost/bin```
+ * Run the Mattermost Server by typing:
+ * ``` ./platform```
+ * You should see a console log like `Server is listening on :8065` letting you know the service is running.
+ * Stop the server for now by typing `ctrl-c`
+1. Setup Mattermost to use the Ubuntu Upstart daemon which handles supervision of the Mattermost process.
+ * ``` sudo touch /etc/init/mattermost.conf```
+ * ``` sudo vi /etc/init/mattermost.conf```
+ * Copy the following lines into `/etc/init/mattermost.conf`
+```
+start on runlevel [2345]
+stop on runlevel [016]
+respawn
+chdir /home/ubuntu/mattermost
+setuid ubuntu
+exec bin/platform
+```
+ * You can manage the process by typing:
+ * ``` sudo start mattermost```
+ * Verify the service is running by typing:
+ * ``` curl http://10.10.10.2:8065```
+ * You should see a page titles *Mattermost - Signup*
+ * You can also stop the process by running the command ` sudo stop mattermost`, but we will skip this step for now.
+
+## Setup Nginx Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.3
+1. We use Nginx for proxying request to the Mattermost Server. The main benefits are:
+ * SSL termination
+ * http to https redirect
+ * Port mapping :80 to :8065
+ * Standard request logs
+1. Install Nginx on Ubuntu with
+ * ``` sudo apt-get install nginx```
+1. Verify Nginx is running
+ * ``` curl http://10.10.10.3```
+ * You should see a *Welcome to nginx!* page
+1. You can manage Nginx with the following commands
+ * ``` sudo service nginx stop```
+ * ``` sudo service nginx start```
+ * ``` sudo service nginx restart```
+1. Map a FQDN (fully qualified domain name) like **mattermost.example.com** to point to the Nginx server.
+1. Configure Nginx to proxy connections from the internet to the Mattermost Server
+ * Create a configuration for Mattermost
+ * ``` sudo touch /etc/nginx/sites-available/mattermost```
+ * Below is a sample configuration with the minimum settings required to configure Mattermost.
+ *
+ ```
+ server {
+ server_name mattermost.example.com;
+ location / {
+ client_max_body_size 50M;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Frame-Options SAMEORIGIN;
+ proxy_pass http://localhost:8065;
+ }
+ }
+```
+ * Remove the existing file with
+ * ``` sudo rm /etc/nginx/sites-enabled/default```
+ * Link the mattermost config by typing:
+ * ```sudo ln -s /etc/nginx/sites-available/mattermost /etc/nginx/sites-enabled/mattermost```
+ * Restart Nginx by typing:
+ * ``` sudo service nginx restart```
+ * Verify you can see Mattermost thru the proxy by typing:
+ * ``` curl http://localhost```
+ * You should see a page titles *Mattermost - Signup*
+
+## Setup Nginx with SSL (Recommended)
+1. You will need a SSL cert from a certificate authority.
+1. For simplicity we will generate a test certificate.
+ * ``` mkdir ~/cert```
+ * ``` cd ~/cert```
+ * ``` sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mattermost.key -out mattermost.crt```
+ * Input the following info
+```
+ Country Name (2 letter code) [AU]:US
+ State or Province Name (full name) [Some-State]:California
+ Locality Name (eg, city) []:Palo Alto
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example LLC
+ Organizational Unit Name (eg, section) []:
+ Common Name (e.g. server FQDN or YOUR name) []:mattermost.example.com
+ Email Address []:admin@mattermost.example.com
+```
+1. Modify the file at `/etc/nginx/sites-available/mattermost` and add the following lines
+ *
+```
+ server {
+ listen 80;
+ server_name mattermost.example.com;
+ return 301 https://$server_name$request_uri;
+ }
+
+ server {
+ listen 443 ssl;
+ server_name mattermost.example.com;
+
+ ssl on;
+ ssl_certificate /home/ubuntu/cert/mattermost.crt;
+ ssl_certificate_key /home/ubuntu/cert/mattermost.key;
+ ssl_session_timeout 5m;
+ ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
+ ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
+ ssl_prefer_server_ciphers on;
+
+ # add to location / above
+ location / {
+ gzip off;
+ proxy_set_header X-Forwarded-Ssl on;
+```
+## Finish Mattermost Server setup
+1. Navigate to https://mattermost.example.com and create a team and user.
+1. The first user in the system is automatically granted the `system_admin` role, which gives you access to the System Console.
+1. From the `town-square` channel click the dropdown and choose the `System Console` option
+1. Update Email Settings. We recommend using an email sending service. The example below assumes AmazonSES.
+ * Set *Send Email Notifications* to true
+ * Set *Require Email Verification* to true
+ * Set *Feedback Name* to `No-Reply`
+ * Set *Feedback Email* to `mattermost@example.com`
+ * Set *SMTP Username* to `AFIADTOVDKDLGERR`
+ * Set *SMTP Password* to `DFKJoiweklsjdflkjOIGHLSDFJewiskdjf`
+ * Set *SMTP Server* to `email-smtp.us-east-1.amazonaws.com`
+ * Set *SMTP Port* to `465`
+ * Set *Connection Security* to `TLS`
+ * Save the Settings
+1. Update File Settings
+ * Change *Local Directory Location* from `./data/` to `/mattermost/data`
+1. Update Log Settings
+ * Set *Log to The Console* to false
+1. Update Rate Limit Settings
+ * Set *Vary By Remote Address* to false
+ * Set *Vary By HTTP Header* to X-Real-IP
+1. Feel free to modify other settings.
+1. Restart the Mattermost Service by typing:
+ * ``` sudo restart mattermost```
diff --git a/doc/integrations/webhook/incoming.md b/doc/integrations/webhook/incoming.md
index a48448cc5..e391cba92 100644
--- a/doc/integrations/webhook/incoming.md
+++ b/doc/integrations/webhook/incoming.md
@@ -20,6 +20,11 @@ You can send the message by including a JSON string as the `payload` parameter i
payload={"text": "Hello, this is some text."}
```
+In addition, if `Content-Type` is specified as `application/json` in the headers of the HTTP request then the body of the request can be direct JSON.
+```
+{"text": "Hello, this is some text."}
+```
+
It is also possible to post richly formatted messages using [Markdown](../../help/enduser/markdown.md).
```
payload={"text": "# A Header\nThe _text_ below **the** header."}
diff --git a/docker/0.5/Dockerfile b/docker/0.5/Dockerfile
deleted file mode 100644
index fec69280e..000000000
--- a/docker/0.5/Dockerfile
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-# See License.txt for license information.
-FROM ubuntu:14.04
-
-# Install Dependancies
-RUN apt-get update && apt-get install -y build-essential
-RUN apt-get install -y curl
-RUN curl -sL https://deb.nodesource.com/setup | bash -
-RUN apt-get install -y nodejs
-RUN apt-get install -y ruby-full
-RUN gem install compass
-
-# Postfix
-RUN apt-get install -y postfix
-
-#
-# Install GO
-#
-
-RUN apt-get update && apt-get install -y \
- gcc libc6-dev make git mercurial \
- --no-install-recommends \
- && rm -rf /var/lib/apt/lists/*
-
-ENV GOLANG_VERSION 1.4.2
-
-RUN curl -sSL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz \
- | tar -v -C /usr/src -xz
-
-RUN cd /usr/src/go/src && ./make.bash --no-clean 2>&1
-
-ENV PATH /usr/src/go/bin:$PATH
-
-RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
-ENV GOPATH /go
-ENV PATH /go/bin:$PATH
-WORKDIR /go
-
-# ---------------------------------------------------------------------------------------------------------------------
-
-#
-# 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-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/*
-
-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 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
-# ---------------------------------------------------------------------------------------------------------------------
-
-#
-# Install Redis
-#
-
-RUN apt-get update && apt-get install -y wget
-RUN wget http://download.redis.io/redis-stable.tar.gz; \
- tar xvzf redis-stable.tar.gz; \
- cd redis-stable; \
- make install
-
-# ---------------------------------------------------------------------------------------------------------------------
-
-# Copy over files
-ADD https://github.com/mattermost/platform/archive/v0.50.tar.gz /go/src/github.com/mattermost/
-RUN mkdir /go/src/github.com/mattermost/platform && tar -zxvf /go/src/github.com/mattermost/v0.50.tar.gz -C /go/src/github.com/mattermost/platform --strip-components=1
-
-# Insert postfix config
-ADD ./main.cf /etc/postfix/
-
-RUN go get github.com/tools/godep
-RUN cd /go/src/github.com/mattermost/platform; godep restore
-RUN go install github.com/mattermost/platform
-RUN cd /go/src/github.com/mattermost/platform/web/react; npm install
-
-RUN chmod +x /go/src/github.com/mattermost/platform/docker/docker-entry.sh
-ENTRYPOINT /go/src/github.com/mattermost/platform/docker/docker-entry.sh
-
-# Ports
-EXPOSE 80
diff --git a/docker/0.5/Dockerrun.aws.json b/docker/0.5/Dockerrun.aws.json
deleted file mode 100755
index 04c7f4224..000000000
--- a/docker/0.5/Dockerrun.aws.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "AWSEBDockerrunVersion": "1",
- "Image": {
- "Name": "mattermost/platform:helium",
- "Update": "true"
- },
- "Ports": [
- {
- "ContainerPort": "80"
- }
- ],
- "Logging": "/var/log/"
-}
diff --git a/docker/0.5/docker-entry.sh b/docker/0.5/docker-entry.sh
deleted file mode 100755
index cfa589041..000000000
--- a/docker/0.5/docker-entry.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/bash
-# Copyright (c) 2015 Spinpunch, 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 postfix
-/etc/init.d/postfix restart
-
-echo starting redis
-redis-server &
-
-echo starting react processor
-cd /go/src/github.com/mattermost/platform/web/react && npm start &
-
-echo starting go web server
-cd /go/src/github.com/mattermost/platform/; go run mattermost.go -config=config_docker.json &
-
-echo starting compass watch
-cd /go/src/github.com/mattermost/platform/web/sass-files && compass watch
diff --git a/docker/0.5/main.cf b/docker/0.5/main.cf
deleted file mode 100644
index ed97d37ef..000000000
--- a/docker/0.5/main.cf
+++ /dev/null
@@ -1,28 +0,0 @@
-myorigin = mattermost.com
-myhostname = mattermost.com
-
-smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
-biff = no
-
-append_dot_mydomain = no
-
-readme_directory = no
-
-# TLS parameters
-smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
-smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
-smtpd_use_tls=no
-smtp_use_tls=no
-smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
-smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
-
-smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
-alias_maps = hash:/etc/aliases
-alias_database = hash:/etc/aliases
-mydestination = localhost, localhost.localdomain, localhost
-relayhost =
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
-mailbox_size_limit = 0
-recipient_delimiter = +
-inet_interfaces = all
-inet_protocols = all
diff --git a/docker/0.6/Dockerrun.aws.zip b/docker/0.6/Dockerrun.aws.zip
deleted file mode 100644
index 342c1549a..000000000
--- a/docker/0.6/Dockerrun.aws.zip
+++ /dev/null
Binary files differ
diff --git a/docker/0.6/config_docker.json b/docker/0.6/config_docker.json
deleted file mode 100644
index b1c72c4bd..000000000
--- a/docker/0.6/config_docker.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "LogSettings": {
- "ConsoleEnable": false,
- "ConsoleLevel": "DEBUG",
- "FileEnable": true,
- "FileLevel": "INFO",
- "FileFormat": "",
- "FileLocation": ""
- },
- "ServiceSettings": {
- "SiteName": "Mattermost",
- "Mode" : "dev",
- "AllowTesting" : false,
- "UseSSL": false,
- "Port": "80",
- "Version": "developer",
- "Shards": {
- },
- "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
- "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
- "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
- "AnalyticsUrl": "",
- "UseLocalStorage": true,
- "StorageDirectory": "/mattermost/data/",
- "AllowedLoginAttempts": 10
- },
- "SSOSettings": {
- "gitlab": {
- "Allow": false,
- "Secret" : "",
- "Id": "",
- "AuthEndpoint": "",
- "TokenEndpoint": "",
- "UserApiEndpoint": ""
- }
- },
- "SqlSettings": {
- "DriverName": "mysql",
- "DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8",
- "DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8"],
- "MaxIdleConns": 10,
- "MaxOpenConns": 10,
- "Trace": false,
- "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
- },
- "AWSSettings": {
- "S3AccessKeyId": "",
- "S3SecretAccessKey": "",
- "S3Bucket": "",
- "S3Region": ""
- },
- "ImageSettings": {
- "ThumbnailWidth": 120,
- "ThumbnailHeight": 100,
- "PreviewWidth": 1024,
- "PreviewHeight": 0,
- "ProfileWidth": 128,
- "ProfileHeight": 128,
- "InitialFont": "luximbi.ttf"
- },
- "EmailSettings": {
- "ByPassEmail" : true,
- "SMTPUsername": "",
- "SMTPPassword": "",
- "SMTPServer": "",
- "UseTLS": false,
- "UseStartTLS": false,
- "FeedbackEmail": "",
- "FeedbackName": "",
- "ApplePushServer": "",
- "ApplePushCertPublic": "",
- "ApplePushCertPrivate": ""
- },
- "RateLimitSettings": {
- "UseRateLimiter": true,
- "PerSec": 10,
- "MemoryStoreSize": 10000,
- "VaryByRemoteAddr": true,
- "VaryByHeader": ""
- },
- "PrivacySettings": {
- "ShowEmailAddress": true,
- "ShowPhoneNumber": true,
- "ShowSkypeId": true,
- "ShowFullName": true
- },
- "TeamSettings": {
- "MaxUsersPerTeam": 150,
- "AllowPublicLink": true,
- "AllowValetDefault": false,
- "TermsLink": "/static/help/configure_links.html",
- "PrivacyLink": "/static/help/configure_links.html",
- "AboutLink": "/static/help/configure_links.html",
- "HelpLink": "/static/help/configure_links.html",
- "ReportProblemLink": "/static/help/configure_links.html",
- "TourLink": "/static/help/configure_links.html",
- "DefaultThemeColor": "#2389D7"
- }
-}
diff --git a/docker/0.6/Dockerfile b/docker/1.0/Dockerfile
index 35ba0bd7e..3d7c36f31 100644
--- a/docker/0.6/Dockerfile
+++ b/docker/1.0/Dockerfile
@@ -13,8 +13,6 @@ ENV MYSQL_DATABASE=mattermost_test
RUN groupadd -r mysql && useradd -r -g mysql mysql
-RUN apt-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/*
-
RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
ENV MYSQL_MAJOR 5.6
@@ -24,7 +22,7 @@ RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
- && apt-get -y install mysql-server \
+ && apt-get -y install perl wget mysql-server \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
@@ -36,8 +34,8 @@ VOLUME /var/lib/mysql
WORKDIR /mattermost
# Copy over files
-ADD https://github.com/mattermost/platform/releases/download/v0.6.0/mattermost.tar.gz /
-RUN tar -zxvf /mattermost.tar.gz --strip-components=1
+ADD https://github.com/mattermost/platform/releases/download/v1.0.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.0/Dockerrun.aws.zip b/docker/1.0/Dockerrun.aws.zip
new file mode 100644
index 000000000..8c2c16e10
--- /dev/null
+++ b/docker/1.0/Dockerrun.aws.zip
Binary files differ
diff --git a/docker/0.6/Dockerrun.aws/.ebextensions/01_files.config b/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config
index 7f40a8b34..7f40a8b34 100644
--- a/docker/0.6/Dockerrun.aws/.ebextensions/01_files.config
+++ b/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config
diff --git a/docker/0.6/Dockerrun.aws/Dockerrun.aws.json b/docker/1.0/Dockerrun.aws/Dockerrun.aws.json
index f6f7cf726..9fdded15e 100755
--- a/docker/0.6/Dockerrun.aws/Dockerrun.aws.json
+++ b/docker/1.0/Dockerrun.aws/Dockerrun.aws.json
@@ -1,7 +1,7 @@
{
"AWSEBDockerrunVersion": "1",
"Image": {
- "Name": "mattermost/platform:0.6",
+ "Name": "mattermost/platform:1.0",
"Update": "true"
},
"Ports": [
diff --git a/docker/1.0/config_docker.json b/docker/1.0/config_docker.json
new file mode 100644
index 000000000..06fee9bd5
--- /dev/null
+++ b/docker/1.0/config_docker.json
@@ -0,0 +1,89 @@
+{
+ "ServiceSettings": {
+ "ListenAddress": ":80",
+ "MaximumLoginAttempts": 10,
+ "SegmentDeveloperKey": "",
+ "GoogleDeveloperKey": "",
+ "EnableOAuthServiceProvider": false,
+ "EnableIncomingWebhooks": false,
+ "EnableTesting": false
+ },
+ "TeamSettings": {
+ "SiteName": "Mattermost",
+ "MaxUsersPerTeam": 50,
+ "EnableTeamCreation": true,
+ "EnableUserCreation": true,
+ "RestrictCreationToDomains": ""
+ },
+ "SqlSettings": {
+ "DriverName": "mysql",
+ "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8",
+ "DataSourceReplicas": [],
+ "MaxIdleConns": 10,
+ "MaxOpenConns": 10,
+ "Trace": false,
+ "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
+ },
+ "LogSettings": {
+ "EnableConsole": false,
+ "ConsoleLevel": "INFO",
+ "EnableFile": true,
+ "FileLevel": "INFO",
+ "FileFormat": "",
+ "FileLocation": ""
+ },
+ "FileSettings": {
+ "DriverName": "local",
+ "Directory": "/mattermost/data/",
+ "EnablePublicLink": true,
+ "PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
+ "ThumbnailWidth": 120,
+ "ThumbnailHeight": 100,
+ "PreviewWidth": 1024,
+ "PreviewHeight": 0,
+ "ProfileWidth": 128,
+ "ProfileHeight": 128,
+ "InitialFont": "luximbi.ttf",
+ "AmazonS3AccessKeyId": "",
+ "AmazonS3SecretAccessKey": "",
+ "AmazonS3Bucket": "",
+ "AmazonS3Region": ""
+ },
+ "EmailSettings": {
+ "EnableSignUpWithEmail": true,
+ "SendEmailNotifications": false,
+ "RequireEmailVerification": false,
+ "FeedbackName": "",
+ "FeedbackEmail": "",
+ "SMTPUsername": "",
+ "SMTPPassword": "",
+ "SMTPServer": "",
+ "SMTPPort": "",
+ "ConnectionSecurity": "",
+ "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
+ "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
+ "ApplePushServer": "",
+ "ApplePushCertPublic": "",
+ "ApplePushCertPrivate": ""
+ },
+ "RateLimitSettings": {
+ "EnableRateLimiter": true,
+ "PerSec": 10,
+ "MemoryStoreSize": 10000,
+ "VaryByRemoteAddr": true,
+ "VaryByHeader": ""
+ },
+ "PrivacySettings": {
+ "ShowEmailAddress": true,
+ "ShowFullName": true
+ },
+ "GitLabSettings": {
+ "Enable": false,
+ "Secret": "",
+ "Id": "",
+ "Scope": "",
+ "AuthEndpoint": "",
+ "TokenEndpoint": "",
+ "UserApiEndpoint": ""
+ }
+} \ No newline at end of file
diff --git a/docker/0.6/docker-entry.sh b/docker/1.0/docker-entry.sh
index ce9f91c40..ce9f91c40 100755
--- a/docker/0.6/docker-entry.sh
+++ b/docker/1.0/docker-entry.sh
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 06fee9bd5..0001eeb0a 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -5,7 +5,7 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": false,
+ "EnableIncomingWebhooks": true,
"EnableTesting": false
},
"TeamSettings": {
@@ -86,4 +86,4 @@
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
-} \ No newline at end of file
+}
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 06fee9bd5..0001eeb0a 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -5,7 +5,7 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": false,
+ "EnableIncomingWebhooks": true,
"EnableTesting": false
},
"TeamSettings": {
@@ -86,4 +86,4 @@
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
-} \ No newline at end of file
+}
diff --git a/model/config.go b/model/config.go
index 853e2bbc0..5d822e263 100644
--- a/model/config.go
+++ b/model/config.go
@@ -16,6 +16,9 @@ const (
IMAGE_DRIVER_LOCAL = "local"
IMAGE_DRIVER_S3 = "amazons3"
+ DATABASE_DRIVER_MYSQL = "mysql"
+ DATABASE_DRIVER_POSTGRES = "postgres"
+
SERVICE_GITLAB = "gitlab"
)
@@ -156,3 +159,92 @@ func ConfigFromJson(data io.Reader) *Config {
return nil
}
}
+
+func (o *Config) IsValid() *AppError {
+
+ if o.ServiceSettings.MaximumLoginAttempts <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum login attempts for service settings. Must be a positive number.", "")
+ }
+
+ if len(o.ServiceSettings.ListenAddress) == 0 {
+ return NewAppError("Config.IsValid", "Invalid listen address for service settings Must be set.", "")
+ }
+
+ if o.TeamSettings.MaxUsersPerTeam <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum users per team for team settings. Must be a positive number.", "")
+ }
+
+ if len(o.SqlSettings.AtRestEncryptKey) != 32 {
+ return NewAppError("Config.IsValid", "Invalid at rest encrypt key for SQL settings. Must be 32 chars.", "")
+ }
+
+ if !(o.SqlSettings.DriverName == DATABASE_DRIVER_MYSQL || o.SqlSettings.DriverName == DATABASE_DRIVER_POSTGRES) {
+ return NewAppError("Config.IsValid", "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'", "")
+ }
+
+ if o.SqlSettings.MaxIdleConns <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum idle connection for SQL settings. Must be a positive number.", "")
+ }
+
+ if len(o.SqlSettings.DataSource) == 0 {
+ return NewAppError("Config.IsValid", "Invalid data source for SQL settings. Must be set.", "")
+ }
+
+ if o.SqlSettings.MaxOpenConns <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum open connection for SQL settings. Must be a positive number.", "")
+ }
+
+ if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) {
+ return NewAppError("Config.IsValid", "Invalid driver name for file settings. Must be 'local' or 'amazons3'", "")
+ }
+
+ if o.FileSettings.PreviewHeight < 0 {
+ return NewAppError("Config.IsValid", "Invalid preview height for file settings. Must be a zero or positive number.", "")
+ }
+
+ if o.FileSettings.PreviewWidth <= 0 {
+ return NewAppError("Config.IsValid", "Invalid preview width for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ProfileHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid profile height for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ProfileWidth <= 0 {
+ return NewAppError("Config.IsValid", "Invalid profile width for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ThumbnailHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid thumbnail height for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ThumbnailHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid thumbnail width for file settings. Must be a positive number.", "")
+ }
+
+ if len(o.FileSettings.PublicLinkSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid public link salt for file settings. Must be 32 chars.", "")
+ }
+
+ if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) {
+ return NewAppError("Config.IsValid", "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'", "")
+ }
+
+ if len(o.EmailSettings.InviteSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid invite salt for email settings. Must be 32 chars.", "")
+ }
+
+ if len(o.EmailSettings.PasswordResetSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid password reset salt for email settings. Must be 32 chars.", "")
+ }
+
+ if o.RateLimitSettings.MemoryStoreSize <= 0 {
+ return NewAppError("Config.IsValid", "Invalid memory store size for rate limit settings. Must be a positive number", "")
+ }
+
+ if o.RateLimitSettings.PerSec <= 0 {
+ return NewAppError("Config.IsValid", "Invalid per sec for rate limit settings. Must be a positive number", "")
+ }
+
+ return nil
+}
diff --git a/model/version.go b/model/version.go
index 8f0c76ebe..233fc3747 100644
--- a/model/version.go
+++ b/model/version.go
@@ -12,7 +12,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
- "0.8.0",
+ "1.0.0",
"0.7.1",
"0.7.0",
"0.6.0",
diff --git a/model/version_test.go b/model/version_test.go
index da40006be..24dbedaa6 100644
--- a/model/version_test.go
+++ b/model/version_test.go
@@ -36,7 +36,7 @@ func TestSplitVersion(t *testing.T) {
}
func TestGetPreviousVersion(t *testing.T) {
- if major, minor := GetPreviousVersion("0.8.0"); major != 0 || minor != 7 {
+ if major, minor := GetPreviousVersion("1.0.0"); major != 0 || minor != 7 {
t.Fatal(major, minor)
}
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index 877246fc3..cb686090e 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -583,7 +583,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto
var query string
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
query = `UPDATE
ChannelMembers
SET
@@ -597,7 +597,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto
Channels.Id = ChannelMembers.ChannelId
AND UserId = :UserId
AND ChannelId = :ChannelId`
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
query = `UPDATE
ChannelMembers, Channels
SET
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 21e8e9d00..668f45fbb 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -418,7 +418,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
var posts []*model.Post
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
// Parse text for wildcards
if wildcard, err := regexp.Compile("\\*($| )"); err == nil {
@@ -451,7 +451,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
}
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
searchQuery := fmt.Sprintf(`SELECT
*
FROM
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index bc1cb2c2c..257054033 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -530,13 +530,6 @@ func TestPostStoreSearch(t *testing.T) {
t.Fatal("returned wrong serach result")
}
- // if utils.Cfg.SqlSettings.DriverName == "mysql" {
- // r2 := (<-store.Post().Search(teamId, userId, "new york", false)).Data.(*model.PostList)
- // if len(r2.Order) >= 1 && r2.Order[0] != o2.Id {
- // t.Fatal("returned wrong serach result")
- // }
- // }
-
r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList)
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
t.Fatal("returned wrong serach result")
diff --git a/store/sql_store.go b/store/sql_store.go
index 6dcf2e8cd..3ef9cfbc4 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -160,9 +160,9 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
if driver == "sqlite3" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}}
- } else if driver == "mysql" {
+ } else if driver == model.DATABASE_DRIVER_MYSQL {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}}
- } else if driver == "postgres" {
+ } else if driver == model.DATABASE_DRIVER_POSTGRES {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}}
} else {
l4g.Critical("Failed to create dialect specific driver")
@@ -183,7 +183,7 @@ func (ss SqlStore) GetCurrentSchemaVersion() string {
}
func (ss SqlStore) DoesTableExist(tableName string) bool {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
count, err := ss.GetMaster().SelectInt(
`SELECT count(relname) FROM pg_class WHERE relname=$1`,
strings.ToLower(tableName),
@@ -197,7 +197,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
return count > 0
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt(
`SELECT
@@ -228,7 +228,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
}
func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
count, err := ss.GetMaster().SelectInt(
`SELECT COUNT(0)
FROM pg_attribute
@@ -251,7 +251,7 @@ func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
return count > 0
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt(
`SELECT
@@ -288,7 +288,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
return false
}
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
l4g.Critical("Failed to create column %v", err)
@@ -298,7 +298,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
return true
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
l4g.Critical("Failed to create column %v", err)
@@ -334,7 +334,7 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo
// func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
// // XXX TODO FIXME this should be removed after 0.6.0
-// if utils.Cfg.SqlSettings.DriverName == "postgres" {
+// if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
// return false
// }
@@ -363,7 +363,7 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st
func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
// It should fail if the index does not exist
if err == nil {
@@ -383,7 +383,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
time.Sleep(time.Second)
panic("Failed to create index " + err.Error())
}
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
if err != nil {
diff --git a/utils/config.go b/utils/config.go
index 5d786699b..3218211e3 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -150,7 +150,12 @@ func LoadConfig(fileName string) {
CfgFileName = fileName
}
+ if err := config.IsValid(); err != nil {
+ panic("Error validating config file=" + fileName + ", err=" + err.Message)
+ }
+
configureLog(&config.LogSettings)
+ TestConnection(&config)
Cfg = &config
SanitizeOptions = getSanitizeOptions(Cfg)
diff --git a/utils/mail.go b/utils/mail.go
index dd975155d..f6fe1ce00 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -65,13 +65,34 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap
return c, nil
}
+func TestConnection(config *model.Config) {
+ if !config.EmailSettings.SendEmailNotifications {
+ return
+ }
+
+ conn, err1 := connectToSMTPServer(config)
+ if err1 != nil {
+ l4g.Error("SMTP server settings do not appear to be configured properly err=%v details=%v", err1.Message, err1.DetailedError)
+ return
+ }
+ defer conn.Close()
+
+ c, err2 := newSMTPClient(conn, config)
+ if err2 != nil {
+ l4g.Error("SMTP connection settings do not appear to be configured properly err=%v details=%v", err2.Message, err2.DetailedError)
+ return
+ }
+ defer c.Quit()
+ defer c.Close()
+}
+
func SendMail(to, subject, body string) *model.AppError {
return SendMailUsingConfig(to, subject, body, Cfg)
}
func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.AppError {
- if !config.EmailSettings.SendEmailNotifications {
+ if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil
}
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 3b5ad2a1a..762a4ab26 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -288,7 +288,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackName'
>
- {'Feedback Name:'}
+ {'Notification Display Name:'}
</label>
<div className='col-sm-8'>
<input
@@ -310,7 +310,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackEmail'
>
- {'Feedback Email:'}
+ {'Notification Email Address:'}
</label>
<div className='col-sm-8'>
<input
diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx
index 1e10c5592..759892ad3 100644
--- a/web/react/components/admin_console/gitlab_settings.jsx
+++ b/web/react/components/admin_console/gitlab_settings.jsx
@@ -12,7 +12,7 @@ export default class GitLabSettings extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
- Allow: this.props.config.GitLabSettings.Allow,
+ Enable: this.props.config.GitLabSettings.Enable,
saveNeeded: false,
serverError: null
};
@@ -21,12 +21,12 @@ export default class GitLabSettings extends React.Component {
handleChange(action) {
var s = {saveNeeded: true, serverError: this.state.serverError};
- if (action === 'AllowTrue') {
- s.Allow = true;
+ if (action === 'EnableTrue') {
+ s.Enable = true;
}
- if (action === 'AllowFalse') {
- s.Allow = false;
+ if (action === 'EnableFalse') {
+ s.Enable = false;
}
this.setState(s);
@@ -37,10 +37,9 @@ export default class GitLabSettings extends React.Component {
$('#save-button').button('loading');
var config = this.props.config;
- config.GitLabSettings.Allow = React.findDOMNode(this.refs.Allow).checked;
+ config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked;
config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim();
config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim();
- config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim();
config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim();
config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim();
@@ -88,7 +87,7 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Allow'
+ htmlFor='Enable'
>
{'Enable Sign Up With GitLab: '}
</label>
@@ -96,21 +95,21 @@ export default class GitLabSettings extends React.Component {
<label className='radio-inline'>
<input
type='radio'
- name='Allow'
+ name='Enable'
value='true'
- ref='Allow'
- defaultChecked={this.props.config.GitLabSettings.Allow}
- onChange={this.handleChange.bind(this, 'AllowTrue')}
+ ref='Enable'
+ defaultChecked={this.props.config.GitLabSettings.Enable}
+ onChange={this.handleChange.bind(this, 'EnableTrue')}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
- name='Allow'
+ name='Enable'
value='false'
- defaultChecked={!this.props.config.GitLabSettings.Allow}
- onChange={this.handleChange.bind(this, 'AllowFalse')}
+ defaultChecked={!this.props.config.GitLabSettings.Enable}
+ onChange={this.handleChange.bind(this, 'EnableFalse')}
/>
{'false'}
</label>
@@ -121,28 +120,6 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Secret'
- >
- {'Secret:'}
- </label>
- <div className='col-sm-8'>
- <input
- type='text'
- className='form-control'
- id='Secret'
- ref='Secret'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
- defaultValue={this.props.config.GitLabSettings.Secret}
- onChange={this.handleChange}
- disabled={!this.state.Allow}
- />
- <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
htmlFor='Id'
>
{'Id:'}
@@ -156,7 +133,7 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
defaultValue={this.props.config.GitLabSettings.Id}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
<p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab'}</p>
</div>
@@ -165,22 +142,22 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Scope'
+ htmlFor='Secret'
>
- {'Scope:'}
+ {'Secret:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
- id='Scope'
- ref='Scope'
- placeholder='Not currently used by GitLab. Please leave blank'
- defaultValue={this.props.config.GitLabSettings.Scope}
+ id='Secret'
+ ref='Secret'
+ placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ defaultValue={this.props.config.GitLabSettings.Secret}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+ <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
</div>
</div>
@@ -200,9 +177,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.AuthEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize).'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -222,9 +199,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.TokenEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -244,9 +221,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.UserApiEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -272,6 +249,30 @@ export default class GitLabSettings extends React.Component {
}
}
+
+//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
+// <div className='form-group'>
+// <label
+// className='control-label col-sm-4'
+// htmlFor='Scope'
+// >
+// {'Scope:'}
+// </label>
+// <div className='col-sm-8'>
+// <input
+// type='text'
+// className='form-control'
+// id='Scope'
+// ref='Scope'
+// placeholder='Not currently used by GitLab. Please leave blank'
+// defaultValue={this.props.config.GitLabSettings.Scope}
+// onChange={this.handleChange}
+// disabled={!this.state.Allow}
+// />
+// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+// </div>
+// </div>
+
GitLabSettings.propTypes = {
config: React.PropTypes.object
};
diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx
index 553dcabe7..fea7085b7 100644
--- a/web/react/components/command_list.jsx
+++ b/web/react/components/command_list.jsx
@@ -70,8 +70,8 @@ export default class CommandList extends React.Component {
className='command-name'
onClick={this.handleClick.bind(this, i)}
>
- <div className='command__title pull-left'><strong>{this.state.suggestions[i].suggestion}</strong></div>
- <div className='command__desc pull-right'>{this.state.suggestions[i].description}</div>
+ <div className='command__title'><strong>{this.state.suggestions[i].suggestion}</strong></div>
+ <div className='command__desc'>{this.state.suggestions[i].description}</div>
</div>
);
}
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index b7bce9b34..54d77c358 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -43,7 +43,7 @@ export default class MoreDirectChannels extends React.Component {
handleClick = function clickHandler(e) {
e.preventDefault();
- utils.switchChannel(channel, channel.teammate_username);
+ utils.switchChannel(channel);
$(React.findDOMNode(self.refs.modal)).modal('hide');
};
} else {
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 4c01d2c43..57a78a0d4 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -62,7 +62,7 @@ export default class NavbarDropdown extends React.Component {
if (currentUser != null) {
isAdmin = Utils.isAdmin(currentUser.roles);
- isSystemAdmin = Utils.isInRole(currentUser.roles, 'system_admin');
+ isSystemAdmin = Utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 37958b649..9d514c741 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -78,7 +78,6 @@ export default class RenameChannelModal extends React.Component {
$(React.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
- Utils.updateTabTitle(channel.display_name);
Utils.updateAddressBar(channel.name);
React.findDOMNode(this.refs.displayName).value = '';
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 32b521560..bdefdbee8 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -47,13 +47,8 @@ export default class SearchResultsItem extends React.Component {
);
var postChannel = ChannelStore.get(this.props.post.channel_id);
- var teammate = '';
- if (postChannel.type === 'D') {
- teammate = utils.getDirectTeammate(this.props.post.channel_id).username;
- }
-
- utils.switchChannel(postChannel, teammate);
+ utils.switchChannel(postChannel);
}
render() {
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 14664ed4d..6033f200f 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -268,14 +268,19 @@ export default class Sidebar extends React.Component {
}
}
updateTitle() {
- var channel = ChannelStore.getCurrent();
+ const channel = ChannelStore.getCurrent();
if (channel) {
+ let currentSiteName = '';
+ if (global.window.config.SiteName != null) {
+ currentSiteName = global.window.config.SiteName;
+ }
+
+ let currentChannelName = channel.display_name;
if (channel.type === 'D') {
- var teammateUsername = Utils.getDirectTeammate(channel.id).username;
- document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-'));
- } else {
- document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
+ currentChannelName = Utils.getDirectTeammate(channel.id).username;
}
+
+ document.title = currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName;
}
}
onScroll() {
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index e63418ae8..708cd04cb 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -33,7 +33,7 @@ export default class SidebarRight extends React.Component {
if (!this.plScrolledToBottom) {
$('.top-visible-post')[0].scrollIntoView();
} else {
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
postHolder.scrollTop(postHolder[0].scrollHeight);
}
}
@@ -51,7 +51,7 @@ export default class SidebarRight extends React.Component {
}
}
render() {
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
const position = postHolder.scrollTop() + postHolder.height() + 14;
const bottom = postHolder[0].scrollHeight;
this.plScrolledToBottom = position >= bottom;
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index f1341d9d7..2df2c8ffd 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -26,11 +26,14 @@ export default class SidebarRightMenu extends React.Component {
var inviteLink = '';
var teamSettingsLink = '';
var manageLink = '';
+ var consoleLink = '';
var currentUser = UserStore.getCurrentUser();
var isAdmin = false;
+ var isSystemAdmin = false;
if (currentUser != null) {
isAdmin = utils.isAdmin(currentUser.roles);
+ isSystemAdmin = utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
@@ -77,6 +80,17 @@ export default class SidebarRightMenu extends React.Component {
);
}
+ if (isSystemAdmin) {
+ consoleLink = (
+ <li>
+ <a
+ href='/admin_console'
+ >
+ <i className='glyphicon glyphicon-wrench'></i>System Console</a>
+ </li>
+ );
+ }
+
var siteName = '';
if (global.window.config.SiteName != null) {
siteName = global.window.config.SiteName;
@@ -107,6 +121,7 @@ export default class SidebarRightMenu extends React.Component {
{inviteLink}
{teamLink}
{manageLink}
+ {consoleLink}
<li>
<a
href='#'
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 1bbfbd162..fa2e2e5e4 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -21,7 +21,7 @@ export default class ManageIncomingHooks extends React.Component {
this.getHooks();
}
addNewHook() {
- let hook = {}; //eslint-disable-line prefer-const
+ const hook = {};
hook.channel_id = this.state.channelId;
Client.addIncomingHook(
@@ -40,13 +40,13 @@ export default class ManageIncomingHooks extends React.Component {
);
}
removeHook(id) {
- let data = {}; //eslint-disable-line prefer-const
+ const data = {};
data.id = id;
Client.deleteIncomingHook(
data,
() => {
- let hooks = this.state.hooks; //eslint-disable-line prefer-const
+ const hooks = this.state.hooks;
let index = -1;
for (let i = 0; i < hooks.length; i++) {
if (hooks[i].id === id) {
@@ -69,7 +69,7 @@ export default class ManageIncomingHooks extends React.Component {
getHooks() {
Client.listIncomingHooks(
(data) => {
- let state = this.state; //eslint-disable-line prefer-const
+ const state = this.state;
if (data) {
state.hooks = data;
@@ -93,9 +93,11 @@ export default class ManageIncomingHooks extends React.Component {
}
const channels = ChannelStore.getAll();
- let options = []; //eslint-disable-line prefer-const
+ const options = [];
channels.forEach((channel) => {
- options.push(<option value={channel.id}>{channel.name}</option>);
+ if (channel.type !== Constants.DM_CHANNEL) {
+ options.push(<option value={channel.id}>{channel.name}</option>);
+ }
});
let disableButton = '';
@@ -103,7 +105,7 @@ export default class ManageIncomingHooks extends React.Component {
disableButton = ' disable';
}
- let hooks = []; //eslint-disable-line prefer-const
+ const hooks = [];
this.state.hooks.forEach((hook) => {
const c = ChannelStore.get(hook.channel_id);
hooks.push(
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index ba14f019f..42c65ef5d 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -420,7 +420,7 @@ export default class NotificationsTab extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
+ <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.config.SiteName + ' for more than 5 minutes.'}</div>
</div>
);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 75e80bc7e..da59f8e5a 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -111,6 +111,7 @@ module.exports = {
],
MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MAX_DMS: 20,
+ DM_CHANNEL: 'D',
MAX_POST_LEN: 4000,
EMOJI_SIZE: 16,
ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index e133ee128..91e47730e 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -78,6 +78,14 @@ export function isAdmin(roles) {
return false;
}
+export function isSystemAdmin(roles) {
+ if (isInRole(roles, 'system_admin')) {
+ return true;
+ }
+
+ return false;
+}
+
export function getDomainWithOutSub() {
var parts = window.location.host.split('.');
@@ -388,7 +396,6 @@ export function toTitleCase(str) {
export function applyTheme(theme) {
if (theme.sidebarBg) {
changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1);
- changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarBg, 1);
}
if (theme.sidebarText) {
@@ -423,6 +430,7 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarHeaderBg, 1);
}
if (theme.sidebarHeaderTextColor) {
@@ -430,8 +438,9 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('.modal .modal-header .modal-title, .modal .modal-header .modal-title .name, .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('#navbar .navbar-default .navbar-brand .heading, ', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('#navbar .navbar-default .navbar-brand .heading', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('#navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('@media(max-width: 768px){.search-bar__container', 'color:' + theme.sidebarHeaderTextColor, 2);
}
if (theme.onlineIndicator) {
@@ -449,29 +458,32 @@ export function applyTheme(theme) {
}
if (theme.centerChannelBg) {
- changeCss('.app__content, .markdown__table, .markdown__table tbody tr', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'background:' + theme.centerChannelBg, 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1);
changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1);
}
if (theme.centerChannelColor) {
- changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1);
+ changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
+ changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
+ changeCss('.mentions--top, .command-box', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1);
changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1);
changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
- changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.command-name', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.custom-textarea', 'color:' + theme.centerChannelColor, 1);
changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2);
changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2);
+ changeCss('.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
@@ -484,7 +496,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before, .command-name:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user:hover .post-body ', 'background: none;', 1);
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
}
@@ -508,7 +520,6 @@ export function applyTheme(theme) {
changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2);
}
}
-
export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
@@ -633,16 +644,12 @@ export function isValidUsername(name) {
return error;
}
-export function updateTabTitle(name) {
- document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
-}
-
export function updateAddressBar(channelName) {
var teamURL = window.location.href.split('/channels')[0];
history.replaceState('data', '', teamURL + '/channels/' + channelName);
}
-export function switchChannel(channel, teammateName) {
+export function switchChannel(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
@@ -651,12 +658,6 @@ export function switchChannel(channel, teammateName) {
updateAddressBar(channel.name);
- if (channel.type === 'D' && teammateName) {
- updateTabTitle(teammateName);
- } else {
- updateTabTitle(channel.display_name);
- }
-
AsyncClient.getChannels(true, true, true);
AsyncClient.getChannelExtraInfo(true);
AsyncClient.getPosts(channel.id);
diff --git a/web/sass-files/sass/partials/_command-box.scss b/web/sass-files/sass/partials/_command-box.scss
index 44eb9b8df..f1aa4dca2 100644
--- a/web/sass-files/sass/partials/_command-box.scss
+++ b/web/sass-files/sass/partials/_command-box.scss
@@ -4,20 +4,29 @@
width: 100%;
border: $border-gray;
bottom: 38px;
+ overflow: auto;
@extend %popover-box-shadow;
+ .sidebar--right & {
+ bottom: 100px;
+ }
}
.command-name {
position: relative;
width: 100%;
- background-color: #fff;
- height: 37px;
- line-height: 37px;
- padding: 2px 10px 2px 5px;
+ line-height: 24px;
+ padding: 5px 10px 8px;
z-index: 101;
+ font-size: 0.95em;
+ border-bottom: 1px solid #ddd;
&:hover {
background-color: #e8eaed;
}
+ .command__desc {
+ margin-left: 5px;
+ @include opacity(0.5);
+ line-height: normal;
+ }
}
.command-desc {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index d29c653ff..9e8d0dc7d 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -553,13 +553,7 @@
padding: 0 10px 0 31px;
background: rgba(black, 0.2);
@include border-radius(3px);
- color: #fff;
- }
- input[type=text] {
- @include input-placeholder {
- color: #fff;
- color: rgba(#fff, 0.5);
- }
+ color: inherit;
}
}
}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 9abdd40da..bcb8b5eac 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -32,8 +32,7 @@
top: 15px;
margin-left: 10px;
font-size: 14px;
- color: #fff;
- color: rgba(#fff, 0.5);
+ @include opacity(0.5);
display: none;
}
.search__form {
diff --git a/web/web.go b/web/web.go
index da7eff13d..bf985a5a0 100644
--- a/web/web.go
+++ b/web/web.go
@@ -850,7 +850,12 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- props := model.MapFromJson(strings.NewReader(r.FormValue("payload")))
+ var props map[string]string
+ if r.Header.Get("Content-Type") == "application/json" {
+ props = model.MapFromJson(r.Body)
+ } else {
+ props = model.MapFromJson(strings.NewReader(r.FormValue("payload")))
+ }
text := props["text"]
if len(text) == 0 {