Merge pull request #765 from mattermost/PLT-349

PLT-349 adding team mgt to admin console
Этот коммит содержится в:
Christopher Speller
2015-09-24 08:14:30 -04:00
родитель 56f76502e3 00112cae51
Коммит 54c7dba33d
24 изменённых файлов: 1283 добавлений и 175 удалений

Просмотреть файл

@@ -87,7 +87,7 @@ func ExportTeams(writer ExportWriter, options *ExportOptions) *model.AppError {
// Get the teams // Get the teams
var teams []*model.Team var teams []*model.Team
if len(options.TeamsToExport) == 0 { if len(options.TeamsToExport) == 0 {
if result := <-Srv.Store.Team().GetForExport(); result.Err != nil { if result := <-Srv.Store.Team().GetAll(); result.Err != nil {
return result.Err return result.Err
} else { } else {
teams = result.Data.([]*model.Team) teams = result.Data.([]*model.Team)

Просмотреть файл

@@ -25,6 +25,7 @@ func InitTeam(r *mux.Router) {
sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST") sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST") sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST")
sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST") sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
sr.Handle("/all", ApiUserRequired(getAll)).Methods("GET")
sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST") sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST") sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST")
sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST") sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST")
@@ -302,6 +303,53 @@ func isTreamCreationAllowed(c *Context, email string) bool {
return true return true
} }
func getAll(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.HasSystemAdminPermissions("getLogs") {
return
}
if result := <-Srv.Store.Team().GetAll(); result.Err != nil {
c.Err = result.Err
return
} else {
teams := result.Data.([]*model.Team)
m := make(map[string]*model.Team)
for _, v := range teams {
m[v.Id] = v
}
w.Write([]byte(model.TeamMapToJson(m)))
}
}
func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
id := props["id"]
if result := <-Srv.Store.Session().Get(id); result.Err != nil {
c.Err = result.Err
return
} else {
session := result.Data.(*model.Session)
c.LogAudit("revoked_all=" + id)
if session.IsOAuth {
RevokeAccessToken(session.Token)
} else {
sessionCache.Remove(session.Token)
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
c.Err = result.Err
return
} else {
w.Write([]byte(model.MapToJson(props)))
return
}
}
}
}
func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) {
m := model.MapFromJson(r.Body) m := model.MapFromJson(r.Body)

Просмотреть файл

@@ -132,6 +132,39 @@ func TestFindTeamByEmail(t *testing.T) {
} }
} }
func TestGetAllTeams(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)
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@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")
if _, err := Client.GetAllTeams(); err == nil {
t.Fatal("you shouldn't have permissions")
}
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
if r1, err := Client.GetAllTeams(); err != nil {
t.Fatal(err)
} else {
teams := r1.Data.(map[string]*model.Team)
if teams[team.Id].Name != team.Name {
t.Fatal()
}
}
}
/* /*
XXXXXX investigate and fix failing test XXXXXX investigate and fix failing test

Просмотреть файл

@@ -51,6 +51,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET") sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("GET") sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("GET")
sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET") sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/sessions", ApiUserRequired(getSessions)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/sessions", ApiUserRequired(getSessions)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/audits", ApiUserRequired(getAudits)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/audits", ApiUserRequired(getAudits)).Methods("GET")
@@ -166,6 +167,19 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
if team.Email == user.Email { if team.Email == user.Email {
user.Roles = model.ROLE_TEAM_ADMIN user.Roles = model.ROLE_TEAM_ADMIN
channelRole = model.CHANNEL_ROLE_ADMIN channelRole = model.CHANNEL_ROLE_ADMIN
// Below is a speical case where the first user in the entire
// system is granted the system_admin role instead of admin
if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil {
c.Err = result.Err
return nil
} else {
count := result.Data.(int64)
if count <= 0 {
user.Roles = model.ROLE_SYSTEM_ADMIN
}
}
} else { } else {
user.Roles = "" user.Roles = ""
} }
@@ -550,13 +564,26 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id, ok := params["id"]
if ok {
// You must be system admin to access another team
if id != c.Session.TeamId {
if !c.HasSystemAdminPermissions("getProfiles") {
return
}
}
etag := (<-Srv.Store.User().GetEtagForProfiles(c.Session.TeamId)).Data.(string) } else {
id = c.Session.TeamId
}
etag := (<-Srv.Store.User().GetEtagForProfiles(id)).Data.(string)
if HandleEtag(etag, w, r) { if HandleEtag(etag, w, r) {
return return
} }
if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil { if result := <-Srv.Store.User().GetProfiles(id); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
@@ -924,8 +951,8 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) { if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set from the command line", "") c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set by another system admin", "")
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
return return
} }
@@ -1155,32 +1182,38 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
hash := props["hash"]
if len(hash) == 0 {
c.SetInvalidParam("resetPassword", "hash")
return
}
data := model.MapFromJson(strings.NewReader(props["data"]))
userId := data["user_id"]
if len(userId) != 26 {
c.SetInvalidParam("resetPassword", "data:user_id")
return
}
timeStr := data["time"]
if len(timeStr) == 0 {
c.SetInvalidParam("resetPassword", "data:time")
return
}
name := props["name"] name := props["name"]
if len(name) == 0 { if len(name) == 0 {
c.SetInvalidParam("resetPassword", "name") c.SetInvalidParam("resetPassword", "name")
return return
} }
userId := props["user_id"]
hash := props["hash"]
timeStr := ""
if !c.IsSystemAdmin() {
if len(hash) == 0 {
c.SetInvalidParam("resetPassword", "hash")
return
}
data := model.MapFromJson(strings.NewReader(props["data"]))
userId = data["user_id"]
timeStr = data["time"]
if len(timeStr) == 0 {
c.SetInvalidParam("resetPassword", "data:time")
return
}
}
if len(userId) != 26 {
c.SetInvalidParam("resetPassword", "user_id")
return
}
c.LogAuditWithUserId(userId, "attempt") c.LogAuditWithUserId(userId, "attempt")
var team *model.Team var team *model.Team
@@ -1205,15 +1238,17 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) { if !c.IsSystemAdmin() {
c.Err = model.NewAppError("resetPassword", "The reset password link does not appear to be valid", "") if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) {
return c.Err = model.NewAppError("resetPassword", "The reset password link does not appear to be valid", "")
} return
}
t, err := strconv.ParseInt(timeStr, 10, 64) t, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
c.Err = model.NewAppError("resetPassword", "The reset link has expired", "") c.Err = model.NewAppError("resetPassword", "The reset link has expired", "")
return return
}
} }
if result := <-Srv.Store.User().UpdatePassword(userId, model.HashPassword(newPassword)); result.Err != nil { if result := <-Srv.Store.User().UpdatePassword(userId, model.HashPassword(newPassword)); result.Err != nil {

Просмотреть файл

@@ -228,6 +228,13 @@ func TestGetUser(t *testing.T) {
ruser2, _ := Client.CreateUser(&user2, "") ruser2, _ := Client.CreateUser(&user2, "")
store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id)) store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id))
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam2, _ := Client.CreateTeam(&team2)
user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser3, _ := Client.CreateUser(&user3, "")
store.Must(Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id))
Client.LoginByEmail(team.Name, user.Email, user.Password) Client.LoginByEmail(team.Name, user.Email, user.Password)
rId := ruser.Data.(*model.User).Id rId := ruser.Data.(*model.User).Id
@@ -276,13 +283,27 @@ func TestGetUser(t *testing.T) {
t.Log(cache_result.Data) t.Log(cache_result.Data)
t.Fatal("cache should be empty") t.Fatal("cache should be empty")
} }
}
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err == nil {
t.Fatal("shouldn't have access")
} }
Client.AuthToken = "" Client.AuthToken = ""
if _, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err == nil { if _, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err == nil {
t.Fatal("shouldn't have accss") t.Fatal("shouldn't have accss")
} }
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
UpdateRoles(c, ruser.Data.(*model.User), model.ROLE_SYSTEM_ADMIN)
Client.LoginByEmail(team.Name, user.Email, "pwd")
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err != nil {
t.Fatal(err)
}
} }
func TestGetAudits(t *testing.T) { func TestGetAudits(t *testing.T) {

Просмотреть файл

@@ -150,6 +150,16 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
} }
} }
func (c *Client) GetAllTeams() (*Result, *AppError) {
if r, err := c.DoApiGet("/teams/all", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil
}
}
func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppError) { func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppError) {
m := make(map[string]string) m := make(map[string]string)
m["name"] = name m["name"] = name
@@ -254,7 +264,7 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
} }
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/profiles", "", etag); err != nil { if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),

Просмотреть файл

@@ -73,6 +73,26 @@ func TeamFromJson(data io.Reader) *Team {
} }
} }
func TeamMapToJson(u map[string]*Team) string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
func TeamMapFromJson(data io.Reader) map[string]*Team {
decoder := json.NewDecoder(data)
var teams map[string]*Team
err := decoder.Decode(&teams)
if err == nil {
return teams
} else {
return nil
}
}
func (o *Team) Etag() string { func (o *Team) Etag() string {
return Etag(o.Id, o.UpdateAt) return Etag(o.Id, o.UpdateAt)
} }

Просмотреть файл

@@ -140,6 +140,24 @@ func (me SqlSessionStore) Remove(sessionIdOrToken string) StoreChannel {
return storeChannel return storeChannel
} }
func (me SqlSessionStore) RemoveAllSessionsForTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
if err != nil {
result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessionsForTeam", "We couldn't remove all the sessions for the team", "id="+teamId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel { func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -80,6 +80,29 @@ func TestSessionRemove(t *testing.T) {
} }
} }
func TestSessionRemoveAll(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
t.Fatal(rs1.Err)
} else {
if rs1.Data.(*model.Session).Id != s1.Id {
t.Fatal("should match")
}
}
Must(store.Session().RemoveAllSessionsForTeam(s1.TeamId))
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed")
}
}
func TestSessionRemoveToken(t *testing.T) { func TestSessionRemoveToken(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -196,7 +196,7 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlTeamStore) GetForExport() StoreChannel { func (s SqlTeamStore) GetAll() StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
go func() { go func() {
@@ -204,7 +204,7 @@ func (s SqlTeamStore) GetForExport() StoreChannel {
var data []*model.Team var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil { if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetForExport", "We could not get all teams", err.Error()) result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "We could not get all teams", err.Error())
} }
result.Data = data result.Data = data

Просмотреть файл

@@ -481,3 +481,22 @@ func (us SqlUserStore) GetForExport(teamId string) StoreChannel {
return storeChannel return storeChannel
} }
func (us SqlUserStore) GetTotalUsersCount() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users"); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetTotalUsersCount", "We could not count the users", err.Error())
} else {
result.Data = count
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -206,6 +206,24 @@ func TestUserStoreGet(t *testing.T) {
} }
} }
func TestUserCountt(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
Must(store.User().Save(&u1))
if result := <-store.User().GetTotalUsersCount(); result.Err != nil {
t.Fatal(result.Err)
} else {
count := result.Data.(int64)
if count <= 0 {
t.Fatal()
}
}
}
func TestUserStoreGetProfiles(t *testing.T) { func TestUserStoreGetProfiles(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -47,7 +47,7 @@ type TeamStore interface {
Get(id string) StoreChannel Get(id string) StoreChannel
GetByName(name string) StoreChannel GetByName(name string) StoreChannel
GetTeamsForEmail(domain string) StoreChannel GetTeamsForEmail(domain string) StoreChannel
GetForExport() StoreChannel GetAll() StoreChannel
} }
type ChannelStore interface { type ChannelStore interface {
@@ -103,6 +103,7 @@ type UserStore interface {
GetEtagForProfiles(teamId string) StoreChannel GetEtagForProfiles(teamId string) StoreChannel
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
GetForExport(teamId string) StoreChannel GetForExport(teamId string) StoreChannel
GetTotalUsersCount() StoreChannel
} }
type SessionStore interface { type SessionStore interface {
@@ -110,6 +111,7 @@ type SessionStore interface {
Get(sessionIdOrToken string) StoreChannel Get(sessionIdOrToken string) StoreChannel
GetSessions(userId string) StoreChannel GetSessions(userId string) StoreChannel
Remove(sessionIdOrToken string) StoreChannel Remove(sessionIdOrToken string) StoreChannel
RemoveAllSessionsForTeam(teamId string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel UpdateRoles(userId string, roles string) StoreChannel
} }

Просмотреть файл

@@ -3,6 +3,7 @@
var AdminSidebar = require('./admin_sidebar.jsx'); var AdminSidebar = require('./admin_sidebar.jsx');
var AdminStore = require('../../stores/admin_store.jsx'); var AdminStore = require('../../stores/admin_store.jsx');
var TeamStore = require('../../stores/team_store.jsx');
var AsyncClient = require('../../utils/async_client.jsx'); var AsyncClient = require('../../utils/async_client.jsx');
var LoadingScreen = require('../loading_screen.jsx'); var LoadingScreen = require('../loading_screen.jsx');
@@ -16,38 +17,104 @@ var GitLabSettingsTab = require('./gitlab_settings.jsx');
var SqlSettingsTab = require('./sql_settings.jsx'); var SqlSettingsTab = require('./sql_settings.jsx');
var TeamSettingsTab = require('./team_settings.jsx'); var TeamSettingsTab = require('./team_settings.jsx');
var ServiceSettingsTab = require('./service_settings.jsx'); var ServiceSettingsTab = require('./service_settings.jsx');
var TeamUsersTab = require('./team_users.jsx');
export default class AdminController extends React.Component { export default class AdminController extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.selectTab = this.selectTab.bind(this); this.selectTab = this.selectTab.bind(this);
this.removeSelectedTeam = this.removeSelectedTeam.bind(this);
this.addSelectedTeam = this.addSelectedTeam.bind(this);
this.onConfigListenerChange = this.onConfigListenerChange.bind(this); this.onConfigListenerChange = this.onConfigListenerChange.bind(this);
this.onAllTeamsListenerChange = this.onAllTeamsListenerChange.bind(this);
var selectedTeams = AdminStore.getSelectedTeams();
if (selectedTeams == null) {
selectedTeams = {};
selectedTeams[TeamStore.getCurrentId()] = 'true';
AdminStore.saveSelectedTeams(selectedTeams);
}
this.state = { this.state = {
config: null, config: AdminStore.getConfig(),
selected: 'service_settings' teams: AdminStore.getAllTeams(),
selectedTeams,
selected: 'service_settings',
selectedTeam: null
}; };
} }
componentDidMount() { componentDidMount() {
AdminStore.addConfigChangeListener(this.onConfigListenerChange); AdminStore.addConfigChangeListener(this.onConfigListenerChange);
AsyncClient.getConfig(); AsyncClient.getConfig();
AdminStore.addAllTeamsChangeListener(this.onAllTeamsListenerChange);
AsyncClient.getAllTeams();
} }
componentWillUnmount() { componentWillUnmount() {
AdminStore.removeConfigChangeListener(this.onConfigListenerChange); AdminStore.removeConfigChangeListener(this.onConfigListenerChange);
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsListenerChange);
} }
onConfigListenerChange() { onConfigListenerChange() {
this.setState({ this.setState({
config: AdminStore.getConfig(), config: AdminStore.getConfig(),
selected: this.state.selected teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
}); });
} }
selectTab(tab) { onAllTeamsListenerChange() {
this.setState({selected: tab}); this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
selectTab(tab, teamId) {
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: tab,
selectedTeam: teamId
});
}
removeSelectedTeam(teamId) {
var selectedTeams = AdminStore.getSelectedTeams();
Reflect.deleteProperty(selectedTeams, teamId);
AdminStore.saveSelectedTeams(selectedTeams);
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
addSelectedTeam(teamId) {
var selectedTeams = AdminStore.getSelectedTeams();
selectedTeams[teamId] = 'true';
AdminStore.saveSelectedTeams(selectedTeams);
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
} }
render() { render() {
@@ -74,6 +141,8 @@ export default class AdminController extends React.Component {
tab = <TeamSettingsTab config={this.state.config} />; tab = <TeamSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'service_settings') { } else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />; tab = <ServiceSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
} }
} }
@@ -85,7 +154,12 @@ export default class AdminController extends React.Component {
/> />
<AdminSidebar <AdminSidebar
selected={this.state.selected} selected={this.state.selected}
selectedTeam={this.state.selectedTeam}
selectTab={this.selectTab} selectTab={this.selectTab}
teams={this.state.teams}
selectedTeams={this.state.selectedTeams}
removeSelectedTeam={this.removeSelectedTeam}
addSelectedTeam={this.addSelectedTeam}
/> />
<div className='inner__wrap channel__wrap'> <div className='inner__wrap channel__wrap'>
<div className='row header'> <div className='row header'>

Просмотреть файл

@@ -2,6 +2,7 @@
// See License.txt for license information. // See License.txt for license information.
var AdminSidebarHeader = require('./admin_sidebar_header.jsx'); var AdminSidebarHeader = require('./admin_sidebar_header.jsx');
var SelectTeamModal = require('./select_team_modal.jsx');
export default class AdminSidebar extends React.Component { export default class AdminSidebar extends React.Component {
constructor(props) { constructor(props) {
@@ -9,28 +10,121 @@ export default class AdminSidebar extends React.Component {
this.isSelected = this.isSelected.bind(this); this.isSelected = this.isSelected.bind(this);
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
this.removeTeam = this.removeTeam.bind(this);
this.showTeamSelect = this.showTeamSelect.bind(this);
this.teamSelectedModal = this.teamSelectedModal.bind(this);
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
this.state = { this.state = {
showSelectModal: false
}; };
} }
handleClick(name, e) { handleClick(name, teamId, e) {
e.preventDefault(); e.preventDefault();
this.props.selectTab(name); this.props.selectTab(name, teamId);
} }
isSelected(name) { isSelected(name, teamId) {
if (this.props.selected === name) { if (this.props.selected === name) {
return 'active'; if (name === 'team_users') {
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
return 'active';
}
} else {
return 'active';
}
} }
return ''; return '';
} }
removeTeam(teamId, e) {
e.preventDefault();
Reflect.deleteProperty(this.props.selectedTeams, teamId);
this.props.removeSelectedTeam(teamId);
if (this.props.selected === 'team_users') {
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
this.props.selectTab('service_settings', null);
}
}
}
componentDidMount() { componentDidMount() {
} }
showTeamSelect(e) {
e.preventDefault();
this.setState({showSelectModal: true});
}
teamSelectedModal(teamId) {
this.props.selectedTeams[teamId] = 'true';
this.setState({showSelectModal: false});
this.props.addSelectedTeam(teamId);
this.forceUpdate();
}
teamSelectedModalDismissed() {
this.setState({showSelectModal: false});
}
render() { render() {
var count = '*';
var teams = 'Loading';
if (this.props.teams != null) {
count = '' + Object.keys(this.props.teams).length;
teams = [];
for (var key in this.props.selectedTeams) {
if (this.props.selectedTeams.hasOwnProperty(key)) {
var team = this.props.teams[key];
if (team != null) {
teams.push(
<ul
key={'team_' + team.id}
className='nav nav__sub-menu'
>
<li>
<a
href='#'
onClick={this.handleClick.bind(this, 'team_users', team.id)}
className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id)}
>
{team.name}
<span
className='menu-icon--right menu__close'
onClick={this.removeTeam.bind(this, team.id)}
style={{cursor: 'pointer'}}
>
{'x'}
</span>
</a>
</li>
<li>
<ul className='nav nav__inner-menu'>
<li>
<a
href='#'
className={this.isSelected('team_users', team.id)}
onClick={this.handleClick.bind(this, 'team_users', team.id)}
>
{'- Users'}
</a>
</li>
</ul>
</li>
</ul>
);
}
}
}
}
return ( return (
<div className='sidebar--left sidebar--collapsable'> <div className='sidebar--left sidebar--collapsable'>
<div> <div>
@@ -38,11 +132,17 @@ export default class AdminSidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'> <ul className='nav nav-pills nav-stacked'>
<li> <li>
<ul className='nav nav__sub-menu'> <ul className='nav nav__sub-menu'>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>{'SETTINGS'}</span>
</h4>
</li>
<li> <li>
<a <a
href='#' href='#'
className={this.isSelected('service_settings')} className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings')} onClick={this.handleClick.bind(this, 'service_settings', null)}
> >
{'Service Settings'} {'Service Settings'}
</a> </a>
@@ -51,7 +151,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('team_settings')} className={this.isSelected('team_settings')}
onClick={this.handleClick.bind(this, 'team_settings')} onClick={this.handleClick.bind(this, 'team_settings', null)}
> >
{'Team Settings'} {'Team Settings'}
</a> </a>
@@ -60,7 +160,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('sql_settings')} className={this.isSelected('sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings')} onClick={this.handleClick.bind(this, 'sql_settings', null)}
> >
{'SQL Settings'} {'SQL Settings'}
</a> </a>
@@ -69,7 +169,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('email_settings')} className={this.isSelected('email_settings')}
onClick={this.handleClick.bind(this, 'email_settings')} onClick={this.handleClick.bind(this, 'email_settings', null)}
> >
{'Email Settings'} {'Email Settings'}
</a> </a>
@@ -78,7 +178,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('image_settings')} className={this.isSelected('image_settings')}
onClick={this.handleClick.bind(this, 'image_settings')} onClick={this.handleClick.bind(this, 'image_settings', null)}
> >
{'File Settings'} {'File Settings'}
</a> </a>
@@ -87,25 +187,16 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('log_settings')} className={this.isSelected('log_settings')}
onClick={this.handleClick.bind(this, 'log_settings')} onClick={this.handleClick.bind(this, 'log_settings', null)}
> >
{'Log Settings'} {'Log Settings'}
</a> </a>
</li> </li>
<li>
<a
href='#'
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs')}
>
{'Logs'}
</a>
</li>
<li> <li>
<a <a
href='#' href='#'
className={this.isSelected('rate_settings')} className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings')} onClick={this.handleClick.bind(this, 'rate_settings', null)}
> >
{'Rate Limit Settings'} {'Rate Limit Settings'}
</a> </a>
@@ -114,7 +205,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('privacy_settings')} className={this.isSelected('privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings')} onClick={this.handleClick.bind(this, 'privacy_settings', null)}
> >
{'Privacy Settings'} {'Privacy Settings'}
</a> </a>
@@ -123,21 +214,65 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('gitlab_settings')} className={this.isSelected('gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings')} onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
> >
{'GitLab Settings'} {'GitLab Settings'}
</a> </a>
</li> </li>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>{'TEAMS (' + count + ')'}</span>
<span className='menu-icon--right'>
<a
href='#'
onClick={this.showTeamSelect}
>
<i className='fa fa-plus'></i>
</a>
</span>
</h4>
</li>
<li>
{teams}
</li>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>{'OTHER'}</span>
</h4>
</li>
<li>
<a
href='#'
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs', null)}
>
{'Logs'}
</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
</div> </div>
<SelectTeamModal
teams={this.props.teams}
show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal}
onModalDismissed={this.teamSelectedModalDismissed}
/>
</div> </div>
); );
} }
} }
AdminSidebar.propTypes = { AdminSidebar.propTypes = {
teams: React.PropTypes.object,
selectedTeams: React.PropTypes.object,
removeSelectedTeam: React.PropTypes.func,
addSelectedTeam: React.PropTypes.func,
selected: React.PropTypes.string, selected: React.PropTypes.string,
selectedTeam: React.PropTypes.string,
selectTab: React.PropTypes.func selectTab: React.PropTypes.func
}; };

Просмотреть файл

@@ -0,0 +1,132 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../../utils/client.jsx');
var Modal = ReactBootstrap.Modal;
export default class ResetPasswordModal extends React.Component {
constructor(props) {
super(props);
this.doSubmit = this.doSubmit.bind(this);
this.doCancel = this.doCancel.bind(this);
this.state = {
serverError: null
};
}
doSubmit(e) {
e.preventDefault();
var password = React.findDOMNode(this.refs.password).value;
if (!password || password.length < 5) {
this.setState({serverError: 'Please enter at least 5 characters.'});
return;
}
this.setState({serverError: null});
var data = {};
data.new_password = password;
data.name = this.props.team.name;
data.user_id = this.props.user.id;
Client.resetPassword(data,
() => {
this.props.onModalSubmit(React.findDOMNode(this.refs.password).value);
},
(err) => {
this.setState({serverError: err.message});
}
);
}
doCancel() {
this.setState({serverError: null});
this.props.onModalDismissed();
}
render() {
if (this.props.user == null) {
return <div/>;
}
let urlClass = 'input-group input-group--limit';
let serverError = null;
if (this.state.serverError) {
urlClass += ' has-error';
serverError = <div className='form-group has-error'><p className='input__help error'>{this.state.serverError}</p></div>;
}
return (
<Modal
show={this.props.show}
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
<Modal.Title>{'Reset Password'}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<Modal.Body>
<div className='form-group'>
<div className='col-sm-10'>
<div className={urlClass}>
<span
data-toggle='tooltip'
title='New Password'
className='input-group-addon'
>
{'New Password'}
</span>
<input
type='password'
ref='password'
className='form-control'
maxLength='22'
autoFocus={true}
tabIndex='1'
/>
</div>
{serverError}
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.doCancel}
>
{'Close'}
</button>
<button
onClick={this.doSubmit}
type='submit'
className='btn btn-primary'
tabIndex='2'
>
{'Select'}
</button>
</Modal.Footer>
</form>
</Modal>
);
}
}
ResetPasswordModal.defaultProps = {
show: false
};
ResetPasswordModal.propTypes = {
user: React.PropTypes.object,
team: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -1,124 +1,99 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
export default class SelectTeam extends React.Component { var Modal = ReactBootstrap.Modal;
export default class SelectTeamModal extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.doSubmit = this.doSubmit.bind(this);
}; this.doCancel = this.doCancel.bind(this);
} }
doSubmit(e) {
e.preventDefault();
this.props.onModalSubmit(React.findDOMNode(this.refs.team).value);
}
doCancel() {
this.props.onModalDismissed();
}
render() { render() {
if (this.props.teams == null) {
return <div/>;
}
var options = [];
for (var key in this.props.teams) {
if (this.props.teams.hasOwnProperty(key)) {
var team = this.props.teams[key];
options.push(
<option
key={'opt_' + team.id}
value={team.id}
>
{team.name}
</option>
);
}
}
return ( return (
<div className='modal fade' <Modal
id='select-team' show={this.props.show}
tabIndex='-1' onHide={this.doCancel}
role='dialog'
aria-labelledby='teamsModalLabel'
> >
<div className='modal-dialog' <Modal.Header closeButton={true}>
role='document' <Modal.Title>{'Select Team'}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
> >
<div className='modal-content'> <Modal.Body>
<div className='modal-header'> <div className='form-group'>
<button <div className='col-sm-12'>
type='button' <select
className='close' ref='team'
data-dismiss='modal' size='10'
aria-label='Close' style={{width: '100%'}}
> >
<span aria-hidden='true'>&times;</span> {options}
</button> </select>
<h4 </div>
className='modal-title'
id='teamsModalLabel'
>
{'Select a team'}
</h4>
</div> </div>
<div className='modal-body'> </Modal.Body>
<table className='more-channel-table table'> <Modal.Footer>
<tbody> <button
<tr> type='button'
<td> className='btn btn-default'
<p className='more-channel-name'>{'Descartes'}</p> onClick={this.doCancel}
</td> >
<td className='td--action'> {'Close'}
<button className='btn btn-primary'>{'Join'}</button> </button>
</td> <button
</tr> onClick={this.doSubmit}
<tr> type='submit'
<td> className='btn btn-primary'
<p className='more-channel-name'>{'Grouping'}</p> tabIndex='2'
</td> >
<td className='td--action'> {'Select'}
<button className='btn btn-primary'>{'Join'}</button> </button>
</td> </Modal.Footer>
</tr> </form>
<tr> </Modal>
<td>
<p className='more-channel-name'>{'Adventure'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Crossroads'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Sky scraping'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Outdoors'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Microsoft'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Apple'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
</tbody>
</table>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>
{'Close'}
</button>
</div>
</div>
</div>
</div>
); );
} }
} }
SelectTeamModal.defaultProps = {
show: false
};
SelectTeamModal.propTypes = {
teams: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -0,0 +1,178 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../../utils/client.jsx');
var LoadingScreen = require('../loading_screen.jsx');
var UserItem = require('./user_item.jsx');
var ResetPasswordModal = require('./reset_password_modal.jsx');
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.getTeamProfiles = this.getTeamProfiles.bind(this);
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
this.doPasswordReset = this.doPasswordReset.bind(this);
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
this.state = {
teamId: props.team.id,
users: null,
serverError: null,
showPasswordModal: false,
user: null
};
}
componentDidMount() {
this.getCurrentTeamProfiles();
}
getCurrentTeamProfiles() {
this.getTeamProfiles(this.props.team.id);
}
// this.setState({
// teamId: this.state.teamId,
// users: this.state.users,
// serverError: this.state.serverError,
// showPasswordModal: this.state.showPasswordModal,
// user: this.state.user
// });
getTeamProfiles(teamId) {
Client.getProfilesForTeam(
teamId,
(users) => {
var memberList = [];
for (var id in users) {
if (users.hasOwnProperty(id)) {
memberList.push(users[id]);
}
}
memberList.sort((a, b) => {
if (a.username < b.username) {
return -1;
}
if (a.username > b.username) {
return 1;
}
return 0;
});
this.setState({
teamId: this.state.teamId,
users: memberList,
serverError: this.state.serverError,
showPasswordModal: this.state.showPasswordModal,
user: this.state.user
});
},
(err) => {
this.setState({
teamId: this.state.teamId,
users: null,
serverError: err.message,
showPasswordModal: this.state.showPasswordModal,
user: this.state.user
});
}
);
}
doPasswordReset(user) {
this.setState({
teamId: this.state.teamId,
users: this.state.users,
serverError: this.state.serverError,
showPasswordModal: true,
user
});
}
doPasswordResetDismiss() {
this.state.showPasswordModal = false;
this.state.user = null;
this.setState({
teamId: this.state.teamId,
users: this.state.users,
serverError: this.state.serverError,
showPasswordModal: false,
user: null
});
}
doPasswordResetSubmit() {
this.setState({
teamId: this.state.teamId,
users: this.state.users,
serverError: this.state.serverError,
showPasswordModal: false,
user: null
});
}
componentWillReceiveProps(newProps) {
this.getTeamProfiles(newProps.team.id);
}
componentWillUnmount() {
}
render() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
if (this.state.users == null) {
return (
<div className='wrapper--fixed'>
<h3>{'Users for ' + this.props.team.name}</h3>
{serverError}
<LoadingScreen />
</div>
);
}
var memberList = this.state.users.map((user) => {
return (
<UserItem
key={'user_' + user.id}
user={user}
refreshProfiles={this.getCurrentTeamProfiles}
doPasswordReset={this.doPasswordReset}
/>);
});
return (
<div className='wrapper--fixed'>
<h3>{'Users for ' + this.props.team.name + ' (' + this.state.users.length + ')'}</h3>
{serverError}
<form
className='form-horizontal'
role='form'
>
<div className='member-list-holder'>
{memberList}
</div>
</form>
<ResetPasswordModal
user={this.state.user}
show={this.state.showPasswordModal}
team={this.props.team}
onModalSubmit={this.doPasswordResetSubmit}
onModalDismissed={this.doPasswordResetDismiss}
/>
</div>
);
}
}
UserList.propTypes = {
team: React.PropTypes.object
};

Просмотреть файл

@@ -0,0 +1,266 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../../utils/client.jsx');
var Utils = require('../../utils/utils.jsx');
export default class UserItem extends React.Component {
constructor(props) {
super(props);
this.handleMakeMember = this.handleMakeMember.bind(this);
this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
this.handleResetPassword = this.handleResetPassword.bind(this);
this.state = {};
}
handleMakeMember(e) {
e.preventDefault();
const data = {
user_id: this.props.user.id,
new_roles: ''
};
Client.updateRoles(data,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeActive(e) {
e.preventDefault();
Client.updateActive(this.props.user.id, true,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeNotActive(e) {
e.preventDefault();
Client.updateActive(this.props.user.id, false,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeAdmin(e) {
e.preventDefault();
const data = {
user_id: this.props.user.id,
new_roles: 'admin'
};
Client.updateRoles(data,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeSystemAdmin(e) {
e.preventDefault();
const data = {
user_id: this.props.user.id,
new_roles: 'system_admin'
};
Client.updateRoles(data,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleResetPassword(e) {
e.preventDefault();
this.props.doPasswordReset(this.props.user);
}
render() {
let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='has-error'>
<label className='has-error control-label'>{this.state.serverError}</label>
</div>
);
}
const user = this.props.user;
let currentRoles = 'Member';
if (user.roles.length > 0) {
if (user.roles.indexOf('system_admin') > -1) {
currentRoles = 'System Admin';
} else {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
}
const email = user.email;
let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin';
let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin';
let showMakeActive = false;
let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
currentRoles = 'Inactive';
currentRoles = 'Inactive';
showMakeMember = false;
showMakeAdmin = false;
showMakeSystemAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
}
let makeSystemAdmin = null;
if (showMakeSystemAdmin) {
makeSystemAdmin = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeSystemAdmin}
>
{'Make System Admin'}
</a>
</li>
);
}
let makeAdmin = null;
if (showMakeAdmin) {
makeAdmin = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeAdmin}
>
{'Make Admin'}
</a>
</li>
);
}
let makeMember = null;
if (showMakeMember) {
makeMember = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeMember}
>
{'Make Member'}
</a>
</li>
);
}
let makeActive = null;
if (showMakeActive) {
makeActive = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeActive}
>
{'Make Active'}
</a>
</li>
);
}
let makeNotActive = null;
if (showMakeNotActive) {
makeNotActive = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeNotActive}
>
{'Make Inactive'}
</a>
</li>
);
}
return (
<div className='row member-div'>
<img
className='post-profile-img pull-left'
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
height='36'
width='36'
/>
<span className='member-name'>{Utils.getDisplayName(user)}</span>
<span className='member-email'>{email}</span>
<div className='dropdown member-drop'>
<a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<span>{currentRoles} </span>
<span className='caret'></span>
</a>
<ul
className='dropdown-menu member-menu'
role='menu'
aria-labelledby='channel_header_dropdown'
>
{makeAdmin}
{makeMember}
{makeActive}
{makeNotActive}
{makeSystemAdmin}
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleResetPassword}
>
{'Reset Password'}
</a>
</li>
</ul>
</div>
{serverError}
</div>
);
}
}
UserItem.propTypes = {
user: React.PropTypes.object.isRequired,
refreshProfiles: React.PropTypes.func.isRequired,
doPasswordReset: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -4,11 +4,14 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var LOG_CHANGE_EVENT = 'log_change'; var LOG_CHANGE_EVENT = 'log_change';
var CONFIG_CHANGE_EVENT = 'config_change'; var CONFIG_CHANGE_EVENT = 'config_change';
var ALL_TEAMS_EVENT = 'all_team_change';
class AdminStoreClass extends EventEmitter { class AdminStoreClass extends EventEmitter {
constructor() { constructor() {
@@ -16,6 +19,7 @@ class AdminStoreClass extends EventEmitter {
this.logs = null; this.logs = null;
this.config = null; this.config = null;
this.teams = null;
this.emitLogChange = this.emitLogChange.bind(this); this.emitLogChange = this.emitLogChange.bind(this);
this.addLogChangeListener = this.addLogChangeListener.bind(this); this.addLogChangeListener = this.addLogChangeListener.bind(this);
@@ -24,6 +28,10 @@ class AdminStoreClass extends EventEmitter {
this.emitConfigChange = this.emitConfigChange.bind(this); this.emitConfigChange = this.emitConfigChange.bind(this);
this.addConfigChangeListener = this.addConfigChangeListener.bind(this); this.addConfigChangeListener = this.addConfigChangeListener.bind(this);
this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this); this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this);
this.emitAllTeamsChange = this.emitAllTeamsChange.bind(this);
this.addAllTeamsChangeListener = this.addAllTeamsChangeListener.bind(this);
this.removeAllTeamsChangeListener = this.removeAllTeamsChangeListener.bind(this);
} }
emitLogChange() { emitLogChange() {
@@ -50,6 +58,18 @@ class AdminStoreClass extends EventEmitter {
this.removeListener(CONFIG_CHANGE_EVENT, callback); this.removeListener(CONFIG_CHANGE_EVENT, callback);
} }
emitAllTeamsChange() {
this.emit(ALL_TEAMS_EVENT);
}
addAllTeamsChangeListener(callback) {
this.on(ALL_TEAMS_EVENT, callback);
}
removeAllTeamsChangeListener(callback) {
this.removeListener(ALL_TEAMS_EVENT, callback);
}
getLogs() { getLogs() {
return this.logs; return this.logs;
} }
@@ -65,6 +85,22 @@ class AdminStoreClass extends EventEmitter {
saveConfig(config) { saveConfig(config) {
this.config = config; this.config = config;
} }
getAllTeams() {
return this.teams;
}
saveAllTeams(teams) {
this.teams = teams;
}
getSelectedTeams() {
return BrowserStore.getItem('seleted_teams');
}
saveSelectedTeams(teams) {
BrowserStore.setItem('seleted_teams', teams);
}
} }
var AdminStore = new AdminStoreClass(); var AdminStore = new AdminStoreClass();
@@ -81,6 +117,10 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
AdminStore.saveConfig(action.config); AdminStore.saveConfig(action.config);
AdminStore.emitConfigChange(); AdminStore.emitConfigChange();
break; break;
case ActionTypes.RECIEVED_ALL_TEAMS:
AdminStore.saveAllTeams(action.teams);
AdminStore.emitAllTeamsChange();
break;
default: default:
} }
}); });

Просмотреть файл

@@ -371,6 +371,32 @@ export function getConfig() {
); );
} }
export function getAllTeams() {
if (isCallInProgress('getAllTeams')) {
return;
}
callTracker.getAllTeams = utils.getTimestamp();
client.getAllTeams(
(data, textStatus, xhr) => {
callTracker.getAllTeams = 0;
if (xhr.status === 304 || !data) {
return;
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ALL_TEAMS,
teams: data
});
},
(err) => {
callTracker.getAllTeams = 0;
dispatchError(err, 'getAllTeams');
}
);
}
export function findTeams(email) { export function findTeams(email) {
if (isCallInProgress('findTeams_' + email)) { if (isCallInProgress('findTeams_' + email)) {
return; return;

Просмотреть файл

@@ -347,6 +347,20 @@ export function testEmail(config, success, error) {
}); });
} }
export function getAllTeams(success, error) {
$.ajax({
url: '/api/v1/teams/all',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success,
error: function onError(xhr, status, err) {
var e = handleError('getAllTeams', xhr, status, err);
error(e);
}
});
}
export function getMeSynchronous(success, error) { export function getMeSynchronous(success, error) {
var currentUser = null; var currentUser = null;
$.ajax({ $.ajax({
@@ -890,6 +904,21 @@ export function getProfiles(success, error) {
}); });
} }
export function getProfilesForTeam(teamId, success, error) {
$.ajax({
cache: false,
url: '/api/v1/users/profiles/' + teamId,
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success,
error: function onError(xhr, status, err) {
var e = handleError('getProfilesForTeam', xhr, status, err);
error(e);
}
});
}
export function uploadFile(formData, success, error) { export function uploadFile(formData, success, error) {
var request = $.ajax({ var request = $.ajax({
url: '/api/v1/files/upload', url: '/api/v1/files/upload',

Просмотреть файл

@@ -35,8 +35,8 @@ module.exports = {
RECIEVED_TEAM: null, RECIEVED_TEAM: null,
RECIEVED_CONFIG: null, RECIEVED_CONFIG: null,
RECIEVED_LOGS: null, RECIEVED_LOGS: null,
RECIEVED_ALL_TEAMS: null,
TOGGLE_IMPORT_THEME_MODAL: null TOGGLE_IMPORT_THEME_MODAL: null
}), }),

Просмотреть файл

@@ -19,6 +19,11 @@
background-color: $primary-color; background-color: $primary-color;
} }
} }
> h4 {
background: #333;
padding: 10px 10px;
margin-top: 1px;
}
} }
.menu-icon--right { .menu-icon--right {
vertical-align: top; vertical-align: top;
@@ -29,10 +34,10 @@
font-size: 13px; font-size: 13px;
right: -2px; right: -2px;
position: relative; position: relative;
color: #fff;
} }
} }
&.nav__sub-menu { &.nav__sub-menu {
padding: 5px 0;
background: #111; background: #111;
-webkit-font-smoothing: auto; -webkit-font-smoothing: auto;
li { li {
@@ -88,6 +93,7 @@
overflow: auto; overflow: auto;
background-color: #f1f1f1; background-color: #f1f1f1;
padding: 0 20px 20px; padding: 0 20px 20px;
min-height: 600px;
} }
.wrapper--fixed { .wrapper--fixed {
max-width: 800px; max-width: 800px;