From 8bf35081c80a56051037d0bc374e9fec3fb9529e Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 10 Sep 2015 14:56:37 -0700 Subject: [PATCH] PLT-12 UI framework for admin console --- api/context.go | 3 +- api/user.go | 31 +- model/user_test.go | 4 + .../admin_console/admin_controller.jsx | 57 +++ .../admin_console/admin_sidebar.jsx | 164 ++++++++ .../admin_console/email_settings.jsx | 311 ++++++++++++++ .../admin_console/jobs_settings.jsx | 183 +++++++++ .../admin_console/select_team_modal.jsx | 124 ++++++ web/react/pages/admin_console.jsx | 13 +- web/templates/admin_console.html | 384 ++---------------- web/web.go | 22 +- 11 files changed, 926 insertions(+), 370 deletions(-) create mode 100644 web/react/components/admin_console/admin_controller.jsx create mode 100644 web/react/components/admin_console/admin_sidebar.jsx create mode 100644 web/react/components/admin_console/email_settings.jsx create mode 100644 web/react/components/admin_console/jobs_settings.jsx create mode 100644 web/react/components/admin_console/select_team_modal.jsx diff --git a/api/context.go b/api/context.go index 2beea2408b..1852ed4d6a 100644 --- a/api/context.go +++ b/api/context.go @@ -285,7 +285,8 @@ func (c *Context) HasPermissionsToChannel(sc store.StoreChannel, where string) b } func (c *Context) IsSystemAdmin() bool { - if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) && IsPrivateIpAddress(c.IpAddress) { + // TODO XXX FIXME && IsPrivateIpAddress(c.IpAddress) + if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) { return true } return false diff --git a/api/user.go b/api/user.go index 48f974dd50..0698ea2f08 100644 --- a/api/user.go +++ b/api/user.go @@ -985,22 +985,25 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { func UpdateRoles(c *Context, user *model.User, roles string) *model.User { // make sure there is at least 1 other active admin - if model.IsInRole(user.Roles, model.ROLE_ADMIN) && !model.IsInRole(roles, model.ROLE_ADMIN) { - if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil { - c.Err = result.Err - return nil - } else { - activeAdmins := -1 - profileUsers := result.Data.(map[string]*model.User) - for _, profileUser := range profileUsers { - if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_ADMIN) { - activeAdmins = activeAdmins + 1 - } - } - if activeAdmins <= 0 { - c.Err = model.NewAppError("updateRoles", "There must be at least one active admin", "") + if !model.IsInRole(roles, model.ROLE_SYSTEM_ADMIN) { + if model.IsInRole(user.Roles, model.ROLE_ADMIN) && !model.IsInRole(roles, model.ROLE_ADMIN) { + if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil { + c.Err = result.Err return nil + } else { + activeAdmins := -1 + profileUsers := result.Data.(map[string]*model.User) + for _, profileUser := range profileUsers { + if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_ADMIN) { + activeAdmins = activeAdmins + 1 + } + } + + if activeAdmins <= 0 { + c.Err = model.NewAppError("updateRoles", "There must be at least one active admin", "") + return nil + } } } } diff --git a/model/user_test.go b/model/user_test.go index 190e5826ee..d9c1a00b6c 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -210,4 +210,8 @@ func TestRoles(t *testing.T) { if !IsInRole("system_admin junk", "system_admin") { t.Fatal() } + + if IsInRole("admin", "system_admin") { + t.Fatal() + } } diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx new file mode 100644 index 0000000000..b069f6432b --- /dev/null +++ b/web/react/components/admin_console/admin_controller.jsx @@ -0,0 +1,57 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var AdminSidebar = require('./admin_sidebar.jsx'); +var EmailTab = require('./email_settings.jsx'); +var JobsTab = require('./jobs_settings.jsx'); +var Navbar = require('../../components/navbar.jsx'); + +export default class AdminController extends React.Component { + constructor(props) { + super(props); + + this.selectTab = this.selectTab.bind(this); + + this.state = { + selected: 'email_settings' + }; + } + + selectTab(tab) { + this.setState({selected: tab}); + } + + render() { + var tab = ''; + + if (this.state.selected === 'email_settings') { + tab = ; + } else if (this.state.selected === 'job_settings') { + tab = ; + } + + return ( +
+ +
+
+ +
+
+
+
+ {tab} +
+
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx new file mode 100644 index 0000000000..64a99b1f42 --- /dev/null +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -0,0 +1,164 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var SidebarHeader = require('../sidebar_header.jsx'); + +export default class AdminSidebar extends React.Component { + constructor(props) { + super(props); + + this.isSelected = this.isSelected.bind(this); + this.handleClick = this.handleClick.bind(this); + + this.state = { + }; + } + + handleClick(name) { + this.props.selectTab(name); + } + + isSelected(name) { + if (this.props.selected === name) { + return 'active'; + } + + return ''; + } + + render() { + return ( +
+
+ + +
+
+ ); + } +} + +AdminSidebar.propTypes = { + selected: React.PropTypes.string, + selectTab: React.PropTypes.func +}; \ No newline at end of file diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx new file mode 100644 index 0000000000..59475dc943 --- /dev/null +++ b/web/react/components/admin_console/email_settings.jsx @@ -0,0 +1,311 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class EmailSettings extends React.Component { + constructor(props) { + super(props); + + this.state = { + }; + } + + render() { + return ( +
+

{'Email Settings'}

+
+
+ +
+ + +

{'This is some sample help text for the Bypass Email field'}

+
+
+
+ +
+ +
+
{' This is some error text for the Bypass Email field'}
+
+

{'This is some sample help text for the SMTP username field'}

+
+
+
+ +
+ +
+
+
+ +
+ +
+ + {'Test Connection'} + +
{' Connection successful'}
+
{' Connection unsuccessful'}
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+ +

{'This is some sample help text for the Apple push server field'}

+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/web/react/components/admin_console/jobs_settings.jsx b/web/react/components/admin_console/jobs_settings.jsx new file mode 100644 index 0000000000..c36d20df70 --- /dev/null +++ b/web/react/components/admin_console/jobs_settings.jsx @@ -0,0 +1,183 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class Jobs extends React.Component { + constructor(props) { + super(props); + + this.state = { + }; + } + + render() { + return ( +
+

{' ************** JOB Settings'}

+
+
+ +
+ + +

{'This is some sample help text for the Bypass Email field'}

+
+
+
+ +
+ +
+
{' This is some error text for the Bypass Email field'}
+
+

{'This is some sample help text for the SMTP username field'}

+
+
+
+
+ +
+
+
+ +
+ +

{'This is some sample help text for the Apple push server field'}

+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx new file mode 100644 index 0000000000..fa30de7b2c --- /dev/null +++ b/web/react/components/admin_console/select_team_modal.jsx @@ -0,0 +1,124 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class SelectTeam extends React.Component { + constructor(props) { + super(props); + + this.state = { + }; + } + + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/web/react/pages/admin_console.jsx b/web/react/pages/admin_console.jsx index 0e14d3ff8c..7bffeab080 100644 --- a/web/react/pages/admin_console.jsx +++ b/web/react/pages/admin_console.jsx @@ -1,8 +1,19 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -export function setupAdminConsolePage() { +var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx'); +var AdminController = require('../components/admin_console/admin_controller.jsx'); +export function setupAdminConsolePage() { + React.render( + , + document.getElementById('admin_controller') + ); + + React.render( + , + document.getElementById('select_team_modal') + ); } global.window.setup_admin_console_page = setupAdminConsolePage; diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html index 9ee5b74ef4..d7fd9217e7 100644 --- a/web/templates/admin_console.html +++ b/web/templates/admin_console.html @@ -4,358 +4,52 @@ {{template "head" . }} + +
- -
- - - - - -
-
- -
-
-
-
- - - -

Email Settings

-
-
- -
- - -

This is some sample help text for the Bypass Email field

-
-
-
- -
- -
-
This is some error text for the Bypass Email field
-
-

This is some sample help text for the SMTP username field

-
-
-
- -
- -
-
-
- -
- -
- Test Connection -
Connection successful
-
Connection unsuccessful
-
-
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
-
-
- - -
-
- -
-
-
- -
- -

This is some sample help text for the Apple push server field

-
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
-
+
+
+
diff --git a/web/web.go b/web/web.go index 7996a49bea..44c9610a6f 100644 --- a/web/web.go +++ b/web/web.go @@ -52,7 +52,7 @@ func InitWeb() { mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET") - + mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET") mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET") mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).Methods("GET") @@ -62,8 +62,7 @@ func InitWeb() { mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(loginCompleteOAuth)).Methods("GET") mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET") - mainrouter.Handle("/admin", api.AppHandlerIndependent(adminConsole)).Methods("GET") - + mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET") // ---------------------------------------------------------------------------------------------- // *ANYTHING* team spefic should go below this line @@ -74,11 +73,9 @@ func InitWeb() { mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET") - mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here. + mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here. mainrouter.Handle("/{team}/channels/{channelname}", api.UserRequired(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here. - mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here. - - + mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here. watchAndParseTemplates() } @@ -644,6 +641,13 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) } func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) { - page := NewHtmlTemplatePage("admin_console", "Admin Console") - page.Render(c, w) + + if !c.IsSystemAdmin() { + c.Err = model.NewAppError("adminConsole", "You do not have permission to access the admin console.", "") + c.Err.StatusCode = http.StatusForbidden + return + } else { + page := NewHtmlTemplatePage("admin_console", "Admin Console") + page.Render(c, w) + } }