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
var teams []*model.Team
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
} else {
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_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).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_teams", ApiAppHandler(findTeams)).Methods("POST")
sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST")
@@ -302,6 +303,53 @@ func isTreamCreationAllowed(c *Context, email string) bool {
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) {
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

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

@@ -51,6 +51,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).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]+}/sessions", ApiUserRequired(getSessions)).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 {
user.Roles = model.ROLE_TEAM_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 {
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) {
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) {
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
return
} else {
@@ -924,8 +951,8 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) {
c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set from the command line", "")
if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set by another system admin", "")
c.Err.StatusCode = http.StatusForbidden
return
}
@@ -1155,32 +1182,38 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
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"]
if len(name) == 0 {
c.SetInvalidParam("resetPassword", "name")
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")
var team *model.Team
@@ -1205,15 +1238,17 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) {
c.Err = model.NewAppError("resetPassword", "The reset password link does not appear to be valid", "")
return
}
if !c.IsSystemAdmin() {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) {
c.Err = model.NewAppError("resetPassword", "The reset password link does not appear to be valid", "")
return
}
t, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
c.Err = model.NewAppError("resetPassword", "The reset link has expired", "")
return
t, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
c.Err = model.NewAppError("resetPassword", "The reset link has expired", "")
return
}
}
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, "")
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)
rId := ruser.Data.(*model.User).Id
@@ -276,13 +283,27 @@ func TestGetUser(t *testing.T) {
t.Log(cache_result.Data)
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 = ""
if _, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err == nil {
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) {

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

@@ -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) {
m := make(map[string]string)
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) {
if r, err := c.DoApiGet("/users/profiles", "", etag); err != nil {
if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
return nil, err
} else {
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 {
return Etag(o.Id, o.UpdateAt)
}

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

@@ -140,6 +140,24 @@ func (me SqlSessionStore) Remove(sessionIdOrToken string) 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 {
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) {
Setup()

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

@@ -196,7 +196,7 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
return storeChannel
}
func (s SqlTeamStore) GetForExport() StoreChannel {
func (s SqlTeamStore) GetAll() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
@@ -204,7 +204,7 @@ func (s SqlTeamStore) GetForExport() StoreChannel {
var data []*model.Team
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

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

@@ -481,3 +481,22 @@ func (us SqlUserStore) GetForExport(teamId string) 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) {
Setup()

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

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

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

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

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

@@ -2,6 +2,7 @@
// See License.txt for license information.
var AdminSidebarHeader = require('./admin_sidebar_header.jsx');
var SelectTeamModal = require('./select_team_modal.jsx');
export default class AdminSidebar extends React.Component {
constructor(props) {
@@ -9,28 +10,121 @@ export default class AdminSidebar extends React.Component {
this.isSelected = this.isSelected.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 = {
showSelectModal: false
};
}
handleClick(name, e) {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name);
this.props.selectTab(name, teamId);
}
isSelected(name) {
isSelected(name, teamId) {
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 '';
}
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() {
}
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() {
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 (
<div className='sidebar--left sidebar--collapsable'>
<div>
@@ -38,11 +132,17 @@ export default class AdminSidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<ul className='nav nav__sub-menu'>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>{'SETTINGS'}</span>
</h4>
</li>
<li>
<a
href='#'
className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings')}
onClick={this.handleClick.bind(this, 'service_settings', null)}
>
{'Service Settings'}
</a>
@@ -51,7 +151,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('team_settings')}
onClick={this.handleClick.bind(this, 'team_settings')}
onClick={this.handleClick.bind(this, 'team_settings', null)}
>
{'Team Settings'}
</a>
@@ -60,7 +160,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings', null)}
>
{'SQL Settings'}
</a>
@@ -69,7 +169,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('email_settings')}
onClick={this.handleClick.bind(this, 'email_settings')}
onClick={this.handleClick.bind(this, 'email_settings', null)}
>
{'Email Settings'}
</a>
@@ -78,7 +178,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('image_settings')}
onClick={this.handleClick.bind(this, 'image_settings')}
onClick={this.handleClick.bind(this, 'image_settings', null)}
>
{'File Settings'}
</a>
@@ -87,25 +187,16 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('log_settings')}
onClick={this.handleClick.bind(this, 'log_settings')}
onClick={this.handleClick.bind(this, 'log_settings', null)}
>
{'Log Settings'}
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs')}
>
{'Logs'}
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings', null)}
>
{'Rate Limit Settings'}
</a>
@@ -114,7 +205,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings', null)}
>
{'Privacy Settings'}
</a>
@@ -123,21 +214,65 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
className={this.isSelected('gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
>
{'GitLab Settings'}
</a>
</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>
</li>
</ul>
</div>
<SelectTeamModal
teams={this.props.teams}
show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal}
onModalDismissed={this.teamSelectedModalDismissed}
/>
</div>
);
}
}
AdminSidebar.propTypes = {
teams: React.PropTypes.object,
selectedTeams: React.PropTypes.object,
removeSelectedTeam: React.PropTypes.func,
addSelectedTeam: React.PropTypes.func,
selected: React.PropTypes.string,
selectedTeam: React.PropTypes.string,
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.
// 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) {
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() {
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 (
<div className='modal fade'
id='select-team'
tabIndex='-1'
role='dialog'
aria-labelledby='teamsModalLabel'
<Modal
show={this.props.show}
onHide={this.doCancel}
>
<div className='modal-dialog'
role='document'
<Modal.Header closeButton={true}>
<Modal.Title>{'Select Team'}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<div className='modal-content'>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4
className='modal-title'
id='teamsModalLabel'
>
{'Select a team'}
</h4>
<Modal.Body>
<div className='form-group'>
<div className='col-sm-12'>
<select
ref='team'
size='10'
style={{width: '100%'}}
>
{options}
</select>
</div>
</div>
<div className='modal-body'>
<table className='more-channel-table table'>
<tbody>
<tr>
<td>
<p className='more-channel-name'>{'Descartes'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<td>
<p className='more-channel-name'>{'Grouping'}</p>
</td>
<td className='td--action'>
<button className='btn btn-primary'>{'Join'}</button>
</td>
</tr>
<tr>
<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>
</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>
);
}
}
}
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 EventEmitter = require('events').EventEmitter;
var BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
var LOG_CHANGE_EVENT = 'log_change';
var CONFIG_CHANGE_EVENT = 'config_change';
var ALL_TEAMS_EVENT = 'all_team_change';
class AdminStoreClass extends EventEmitter {
constructor() {
@@ -16,6 +19,7 @@ class AdminStoreClass extends EventEmitter {
this.logs = null;
this.config = null;
this.teams = null;
this.emitLogChange = this.emitLogChange.bind(this);
this.addLogChangeListener = this.addLogChangeListener.bind(this);
@@ -24,6 +28,10 @@ class AdminStoreClass extends EventEmitter {
this.emitConfigChange = this.emitConfigChange.bind(this);
this.addConfigChangeListener = this.addConfigChangeListener.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() {
@@ -50,6 +58,18 @@ class AdminStoreClass extends EventEmitter {
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() {
return this.logs;
}
@@ -65,6 +85,22 @@ class AdminStoreClass extends EventEmitter {
saveConfig(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();
@@ -81,6 +117,10 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
AdminStore.saveConfig(action.config);
AdminStore.emitConfigChange();
break;
case ActionTypes.RECIEVED_ALL_TEAMS:
AdminStore.saveAllTeams(action.teams);
AdminStore.emitAllTeamsChange();
break;
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) {
if (isCallInProgress('findTeams_' + email)) {
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) {
var currentUser = null;
$.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) {
var request = $.ajax({
url: '/api/v1/files/upload',

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

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

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

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