PLT-349 adding team mgt to admin console

Этот коммит содержится в:
=Corey Hulen
2015-09-23 12:49:28 -07:00
родитель 9e04909c0a
Коммит 1626a6de6f
22 изменённых файлов: 1154 добавлений и 166 удалений

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

@@ -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")
@@ -553,13 +554,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 {
@@ -1158,32 +1172,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
@@ -1208,15 +1228,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

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

@@ -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 {
@@ -110,6 +110,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)}
> >
{'Image Settings'} {'Image Settings'}
</a> </a>
@@ -87,7 +187,7 @@ 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>
@@ -96,7 +196,7 @@ export default class AdminSidebar extends React.Component {
<a <a
href='#' href='#'
className={this.isSelected('logs')} className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs')} onClick={this.handleClick.bind(this, 'logs', null)}
> >
{'Logs'} {'Logs'}
</a> </a>
@@ -105,7 +205,7 @@ export default class AdminSidebar extends React.Component {
<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 +214,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 +223,50 @@ 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>
</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,136 @@
// 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.state.serverError = 'Please enter at least 5 characters.';
this.setState(this.state);
return;
}
this.state.serverError = null;
this.setState(this.state);
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.state.serverError = err.message;
this.setState(this.state);
}
);
}
doCancel() {
this.state.serverError = null;
this.setState(this.state);
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,147 @@
// 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);
}
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.state.users = memberList;
this.setState(this.state);
},
(err) => {
this.state.serverError = err.message;
this.state.users = null;
this.setState(this.state);
}
);
}
doPasswordReset(user) {
this.state.showPasswordModal = true;
this.state.user = user;
this.setState(this.state);
}
doPasswordResetDismiss() {
this.state.showPasswordModal = false;
this.state.user = null;
this.setState(this.state);
}
doPasswordResetSubmit() {
this.state.showPasswordModal = false;
this.state.user = null;
this.setState(this.state);
}
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,230 @@
// 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.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});
}
);
}
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 showMakeActive = false;
let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
currentRoles = 'Inactive';
currentRoles = 'Inactive';
showMakeMember = false;
showMakeAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
}
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}
<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
}), }),
PayloadSources: keyMirror({ PayloadSources: keyMirror({

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

@@ -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;