diff options
-rw-r--r-- | api/command.go | 178 | ||||
-rw-r--r-- | api/command_echo.go | 81 | ||||
-rw-r--r-- | api/command_echo_test.go | 42 | ||||
-rw-r--r-- | api/command_test.go | 130 | ||||
-rw-r--r-- | api/user.go | 4 | ||||
-rw-r--r-- | config/config.json | 41 | ||||
-rw-r--r-- | docker/dev/config_docker.json | 2 | ||||
-rw-r--r-- | docker/local/config_docker.json | 2 | ||||
-rw-r--r-- | model/client.go | 20 | ||||
-rw-r--r-- | model/command_response.go | 40 | ||||
-rw-r--r-- | model/command_response_test.go | 19 | ||||
-rw-r--r-- | model/config.go | 44 | ||||
-rw-r--r-- | model/version.go | 10 | ||||
-rw-r--r-- | utils/config.go | 2 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 7 | ||||
-rw-r--r-- | web/react/utils/async_client.jsx | 20 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 16 |
17 files changed, 579 insertions, 79 deletions
diff --git a/api/command.go b/api/command.go index cff30cdbb..1e67453fb 100644 --- a/api/command.go +++ b/api/command.go @@ -4,45 +4,169 @@ package api import ( - // "io" - // "net/http" + //"io" + "net/http" // "path" // "strconv" - // "strings" + "strings" // "time" l4g "code.google.com/p/log4go" "github.com/gorilla/mux" - // "github.com/mattermost/platform/model" - // "github.com/mattermost/platform/utils" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" ) -// type commandHandler func(c *Context, command *model.Command) bool +type CommandProvider interface { + GetCommand() *model.Command + DoCommand(c *Context, channelId string, message string) *model.CommandResponse +} -// var ( -// cmds = map[string]string{ -// "logoutCommand": "/logout", -// "joinCommand": "/join", -// "loadTestCommand": "/loadtest", -// "echoCommand": "/echo", -// "shrugCommand": "/shrug", -// "meCommand": "/me", -// } -// commands = []commandHandler{ -// logoutCommand, -// joinCommand, -// loadTestCommand, -// echoCommand, -// shrugCommand, -// meCommand, -// } -// commandNotImplementedErr = model.NewAppError("checkCommand", "Command not implemented", "") -// ) -// var echoSem chan bool +var commandProviders = make(map[string]CommandProvider) + +func RegisterCommandProvider(newProvider CommandProvider) { + commandProviders[newProvider.GetCommand().Trigger] = newProvider +} + +func GetCommandProvidersProvider(name string) CommandProvider { + provider, ok := commandProviders[name] + if ok { + return provider + } + + return nil +} func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") - // r.Handle("/command", ApiUserRequired(command)).Methods("POST") + + sr := r.PathPrefix("/commands").Subrouter() + + sr.Handle("/execute", ApiUserRequired(execute)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST") + + sr.Handle("/create", ApiUserRequired(create)).Methods("POST") + sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET") + // sr.Handle("/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST") + // sr.Handle("/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST") +} + +func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { + commands := make([]*model.Command, 0, 32) + for _, value := range commandProviders { + cpy := *value.GetCommand() + cpy.Token = "" + cpy.CreatorId = "" + cpy.Method = "" + cpy.URL = "" + cpy.Username = "" + cpy.IconURL = "" + commands = append(commands, &cpy) + } + + w.Write([]byte(model.CommandListToJson(commands))) +} + +func execute(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + command := strings.TrimSpace(props["command"]) + channelId := strings.TrimSpace(props["channelId"]) + + if len(command) <= 1 || strings.Index(command, "/") != 0 { + c.Err = model.NewAppError("command", "Command must start with /", "") + return + } + + if len(channelId) > 0 { + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) + + if !c.HasPermissionsToChannel(cchan, "checkCommand") { + return + } + } + + parts := strings.Split(command, " ") + trigger := parts[0][1:] + provider := GetCommandProvidersProvider(trigger) + + if provider != nil { + message := strings.Join(parts[1:], " ") + response := provider.DoCommand(c, channelId, message) + + if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { + post := &model.Post{} + post.ChannelId = channelId + post.Message = response.Text + if _, err := CreatePost(c, post, true); err != nil { + c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") + } + } + + w.Write([]byte(response.ToJson())) + } else { + c.Err = model.NewAppError("command", "Command with a trigger of '"+trigger+"' not found", "") + } +} + +func create(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + c.LogAudit("attempt") + + cmd := model.CommandFromJson(r.Body) + + if cmd == nil { + c.SetInvalidParam("createCommand", "command") + return + } + + cmd.CreatorId = c.Session.UserId + cmd.TeamId = c.Session.TeamId + + if result := <-Srv.Store.Command().Save(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + c.LogAudit("success") + rcmd := result.Data.(*model.Command) + w.Write([]byte(rcmd.ToJson())) + } +} + +func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else { + cmds := result.Data.([]*model.Command) + w.Write([]byte(model.CommandListToJson(cmds))) + } } // func command(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api/command_echo.go b/api/command_echo.go new file mode 100644 index 000000000..5d34578c8 --- /dev/null +++ b/api/command_echo.go @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strconv" + "strings" + "time" + + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" +) + +var echoSem chan bool + +type EchoProvider struct { +} + +func init() { + RegisterCommandProvider(&EchoProvider{}) +} + +func (me *EchoProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "echo", + AutoComplete: true, + AutoCompleteDesc: "Echo back text from your account", + AutoCompleteHint: "\"message\" [delay in seconds]", + DisplayName: "echo", + } +} + +func (me *EchoProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + maxThreads := 100 + + delay := 0 + if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { + if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { + delay = checkDelay + } + message = message[1:endMsg] + } else if strings.Index(message, " ") > -1 { + delayIdx := strings.LastIndex(message, " ") + delayStr := strings.Trim(message[delayIdx:], " ") + + if checkDelay, err := strconv.Atoi(delayStr); err == nil { + delay = checkDelay + message = message[:delayIdx] + } + } + + if delay > 10000 { + return &model.CommandResponse{Text: "Delays must be under 10000 seconds", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if echoSem == nil { + // We want one additional thread allowed so we never reach channel lockup + echoSem = make(chan bool, maxThreads+1) + } + + if len(echoSem) >= maxThreads { + return &model.CommandResponse{Text: "High volume of echo request, cannot process request", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + echoSem <- true + go func() { + defer func() { <-echoSem }() + post := &model.Post{} + post.ChannelId = channelId + post.Message = message + + time.Sleep(time.Duration(delay) * time.Second) + + if _, err := CreatePost(c, post, true); err != nil { + l4g.Error("Unable to create /echo post, err=%v", err) + } + }() + + return &model.CommandResponse{} +} diff --git a/api/command_echo_test.go b/api/command_echo_test.go new file mode 100644 index 000000000..40a0bb4b9 --- /dev/null +++ b/api/command_echo_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestEchoCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + echoTestString := "/echo test" + + r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Echo command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Echo command failed to send") + } +} diff --git a/api/command_test.go b/api/command_test.go index 8b996b9eb..8e0c2580e 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -3,15 +3,127 @@ package api -// import ( -// "strings" -// "testing" -// "time" - -// "github.com/mattermost/platform/model" -// "github.com/mattermost/platform/store" -// "github.com/mattermost/platform/utils" -// ) +import ( + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +func TestListCommands(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + if results, err := Client.ListCommands(); err != nil { + t.Fatal(err) + } else { + commands := results.Data.([]*model.Command) + foundEcho := false + + for _, command := range commands { + if command.Trigger == "echo" { + foundEcho = true + } + } + + if !foundEcho { + t.Fatal("Couldn't find echo command") + } + } +} + +func TestCreateCommand(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + + if _, err := Client.CreateCommand(cmd); err == nil { + t.Fatal("should have failed because not admin") + } + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + var rcmd *model.Command + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + rcmd = result.Data.(*model.Command) + } + + if rcmd.CreatorId != user.Id { + t.Fatal("user ids didn't match") + } + + if rcmd.TeamId != team.Id { + t.Fatal("team ids didn't match") + } + + cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + if result.Data.(*model.Command).CreatorId != user.Id { + t.Fatal("bad user id wasn't overwritten") + } + if result.Data.(*model.Command).TeamId != team.Id { + t.Fatal("bad team id wasn't overwritten") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + +func TestListTeamCommands(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) + + if result, err := Client.ListTeamCommands(); err != nil { + t.Fatal(err) + } else { + cmds := result.Data.([]*model.Command) + + if len(hooks) != 1 { + t.Fatal("incorrect number of cmd") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} // func TestSuggestRootCommands(t *testing.T) { // Setup() diff --git a/api/user.go b/api/user.go index d4c7fcaf5..494296fb5 100644 --- a/api/user.go +++ b/api/user.go @@ -1435,6 +1435,10 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError { return result.Err } + if result := <-Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { return result.Err } diff --git a/config/config.json b/config/config.json index c43db1e50..b3822692e 100644 --- a/config/config.json +++ b/config/config.json @@ -5,17 +5,19 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, - "EnableOutgoingWebhooks": false, - "EnablePostUsernameOverride": false, - "EnablePostIconOverride": false, + "EnableIncomingWebhooks": true, + "EnableOutgoingWebhooks": true, + "EnableCommands": true, + "EnableOnlyAdminIntegrations": true, + "EnablePostUsernameOverride": true, + "EnablePostIconOverride": true, "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, - "SessionLengthWebInDays" : 30, - "SessionLengthMobileInDays" : 30, - "SessionLengthSSOInDays" : 30, - "SessionCacheInMinutes" : 10 + "SessionLengthWebInDays": 30, + "SessionLengthMobileInDays": 30, + "SessionLengthSSOInDays": 30, + "SessionCacheInMinutes": 10 }, "TeamSettings": { "SiteName": "Mattermost", @@ -107,5 +109,28 @@ "AuthEndpoint": "", "TokenEndpoint": "", "UserApiEndpoint": "" + }, + "GoogleSettings": { + "Enable": false, + "Secret": "", + "Id": "", + "Scope": "", + "AuthEndpoint": "", + "TokenEndpoint": "", + "UserApiEndpoint": "" + }, + "LdapSettings": { + "Enable": false, + "LdapServer": null, + "LdapPort": 389, + "BaseDN": null, + "BindUsername": null, + "BindPassword": null, + "FirstNameAttribute": null, + "LastNameAttribute": null, + "EmailAttribute": null, + "UsernameAttribute": null, + "IdAttribute": null, + "QueryTimeout": 60 } }
\ No newline at end of file diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index 1aa2ee843..cbe617d3a 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -7,6 +7,8 @@ "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": false, "EnableOutgoingWebhooks": false, + "EnableCommands": false, + "EnableOnlyAdminIntegrations": false, "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, "EnableTesting": false, diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index 1aa2ee843..cbe617d3a 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -7,6 +7,8 @@ "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": false, "EnableOutgoingWebhooks": false, + "EnableCommands": false, + "EnableOnlyAdminIntegrations": false, "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, "EnableTesting": false, diff --git a/model/client.go b/model/client.go index f1773f3c7..83d1d316c 100644 --- a/model/client.go +++ b/model/client.go @@ -372,7 +372,25 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul m["command"] = command m["channelId"] = channelId m["suggest"] = strconv.FormatBool(suggest) - if r, err := c.DoApiPost("/command", MapToJson(m)); err != nil { + if r, err := c.DoApiPost("/commands/execute", MapToJson(m)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil + } +} + +func (c *Client) ListCommands() (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/list", ""); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil + } +} + +func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/create", cmd.ToJson()); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/model/command_response.go b/model/command_response.go new file mode 100644 index 000000000..001384864 --- /dev/null +++ b/model/command_response.go @@ -0,0 +1,40 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + COMMAND_RESPONSE_TYPE_IN_CHANNEL = "in_channel" + COMMAND_RESPONSE_TYPE_EPHEMERAL = "ephemeral" +) + +type CommandResponse struct { + ResponseType string `json:"response_type"` + Text string `json:"text"` + Attachments interface{} `json:"attachments"` +} + +func (o *CommandResponse) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func CommandResponseFromJson(data io.Reader) *CommandResponse { + decoder := json.NewDecoder(data) + var o CommandResponse + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/model/command_response_test.go b/model/command_response_test.go new file mode 100644 index 000000000..7aa3e984b --- /dev/null +++ b/model/command_response_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "strings" + "testing" +) + +func TestCommandResponseJson(t *testing.T) { + o := CommandResponse{Text: "test"} + json := o.ToJson() + ro := CommandResponseFromJson(strings.NewReader(json)) + + if o.Text != ro.Text { + t.Fatal("Ids do not match") + } +} diff --git a/model/config.go b/model/config.go index ed56ed0c7..7d9ff41f1 100644 --- a/model/config.go +++ b/model/config.go @@ -24,22 +24,24 @@ const ( ) type ServiceSettings struct { - ListenAddress string - MaximumLoginAttempts int - SegmentDeveloperKey string - GoogleDeveloperKey string - EnableOAuthServiceProvider bool - EnableIncomingWebhooks bool - EnableOutgoingWebhooks bool - EnablePostUsernameOverride bool - EnablePostIconOverride bool - EnableTesting bool - EnableDeveloper *bool - EnableSecurityFixAlert *bool - SessionLengthWebInDays *int - SessionLengthMobileInDays *int - SessionLengthSSOInDays *int - SessionCacheInMinutes *int + ListenAddress string + MaximumLoginAttempts int + SegmentDeveloperKey string + GoogleDeveloperKey string + EnableOAuthServiceProvider bool + EnableIncomingWebhooks bool + EnableOutgoingWebhooks bool + EnableCommands *bool + EnableOnlyAdminIntegrations *bool + EnablePostUsernameOverride bool + EnablePostIconOverride bool + EnableTesting bool + EnableDeveloper *bool + EnableSecurityFixAlert *bool + SessionLengthWebInDays *int + SessionLengthMobileInDays *int + SessionLengthSSOInDays *int + SessionCacheInMinutes *int } type SSOSettings struct { @@ -330,6 +332,16 @@ func (o *Config) SetDefaults() { o.ServiceSettings.SessionCacheInMinutes = new(int) *o.ServiceSettings.SessionCacheInMinutes = 10 } + + if o.ServiceSettings.EnableCommands == nil { + o.ServiceSettings.EnableCommands = new(bool) + *o.ServiceSettings.EnableCommands = false + } + + if o.ServiceSettings.EnableOnlyAdminIntegrations == nil { + o.ServiceSettings.EnableOnlyAdminIntegrations = new(bool) + *o.ServiceSettings.EnableOnlyAdminIntegrations = true + } } func (o *Config) IsValid() *AppError { diff --git a/model/version.go b/model/version.go index 142ddb371..e6faf137c 100644 --- a/model/version.go +++ b/model/version.go @@ -24,10 +24,10 @@ var versions = []string{ } var CurrentVersion string = versions[0] -var BuildNumber = "_BUILD_NUMBER_" -var BuildDate = "_BUILD_DATE_" -var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" +var BuildNumber = "dev" +var BuildDate = "Fri Jan 8 14:19:26 UTC 2016" +var BuildHash = "001a4448ca5fb0018eeb442915b473b121c04bf3" +var BuildEnterpriseReady = "false" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") @@ -73,7 +73,7 @@ func GetPreviousVersion(currentVersion string) (int64, int64) { } func IsOfficalBuild() bool { - return BuildNumber != "_BUILD_NUMBER_" + return BuildNumber != "dev" } func IsCurrentVersion(versionToCheck string) bool { diff --git a/utils/config.go b/utils/config.go index 18bd15241..1e6b58ced 100644 --- a/utils/config.go +++ b/utils/config.go @@ -194,6 +194,8 @@ func getClientConfig(c *model.Config) map[string]string { props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks) props["EnableOutgoingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableOutgoingWebhooks) + props["EnableCommands"] = strconv.FormatBool(*c.ServiceSettings.EnableCommands) + props["EnableOnlyAdminIntegrations"] = strconv.FormatBool(*c.ServiceSettings.EnableOnlyAdminIntegrations) props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride) props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride) props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper) diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index e901b272a..72bf83e43 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -134,15 +134,10 @@ export default class CreatePost extends React.Component { post.message, false, (data) => { - if (data.response === 'not implemented') { - this.sendMessage(post); - return; - } - PostStore.storeDraft(data.channel_id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - if (data.goto_location.length > 0) { + if (data.goto_location && data.goto_location.length > 0) { window.location.href = data.goto_location; } }, diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index f218270da..78be646ac 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -747,20 +747,28 @@ export function savePreferences(preferences, success, error) { } export function getSuggestedCommands(command, suggestionId, component) { - client.executeCommand( - '', - command, - true, + client.listCommands( (data) => { + var matches = []; + data.forEach((cmd) => { + if (('/' + cmd.trigger).indexOf(command) === 0) { + matches.push({ + suggestion: '/' + cmd.trigger + ' ' + cmd.auto_complete_hint, + description: cmd.auto_complete_desc + }); + } + }); + // pull out the suggested commands from the returned data - const terms = data.suggestions.map((suggestion) => suggestion.suggestion); + //const terms = matches.map((suggestion) => suggestion.trigger); + const terms = matches.map((suggestion) => suggestion.suggestion); AppDispatcher.handleServerAction({ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, id: suggestionId, matchedPretext: command, terms, - items: data.suggestions, + items: matches, component }); }, diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index e1c331aff..56c8c4b3e 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -839,7 +839,7 @@ export function getChannelExtraInfo(id, success, error) { export function executeCommand(channelId, command, suggest, success, error) { $.ajax({ - url: '/api/v1/command', + url: '/api/v1/commands/execute', dataType: 'json', contentType: 'application/json', type: 'POST', @@ -852,6 +852,20 @@ export function executeCommand(channelId, command, suggest, success, error) { }); } +export function listCommands(success, error) { + $.ajax({ + url: '/api/v1/commands/list', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + success, + error: function onError(xhr, status, err) { + var e = handleError('listCommands', xhr, status, err); + error(e); + } + }); +} + export function getPostsPage(channelId, offset, limit, success, error, complete) { $.ajax({ cache: false, |