PLT-1830 Adding ability to create team with ldap
Этот коммит содержится в:
76
api/team.go
76
api/team.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mattermost/platform/einterfaces"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
@@ -25,6 +26,7 @@ func InitTeam(r *mux.Router) {
|
|||||||
sr := r.PathPrefix("/teams").Subrouter()
|
sr := r.PathPrefix("/teams").Subrouter()
|
||||||
sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
|
sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
|
||||||
sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
|
sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
|
||||||
|
sr.Handle("/create_with_ldap", ApiAppHandler(createTeamWithLdap)).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("/all", ApiUserRequired(getAll)).Methods("GET")
|
||||||
@@ -244,6 +246,80 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createTeamWithLdap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ldap := einterfaces.GetLdapInterface()
|
||||||
|
if ldap == nil {
|
||||||
|
c.Err = model.NewLocAppError("createTeamWithLdap", "ent.ldap.do_login.licence_disable.app_error", nil, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamSignup := model.TeamSignupFromJson(r.Body)
|
||||||
|
|
||||||
|
if teamSignup == nil {
|
||||||
|
c.SetInvalidParam("createTeam", "teamSignup")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamSignup.Team.PreSave()
|
||||||
|
|
||||||
|
if err := teamSignup.Team.IsValid(*utils.Cfg.TeamSettings.RestrictTeamNames); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isTeamCreationAllowed(c, teamSignup.Team.Email) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamSignup.Team.Id = ""
|
||||||
|
|
||||||
|
found := FindTeamByName(c, teamSignup.Team.Name, "true")
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
c.Err = model.NewLocAppError("createTeamFromSignup", "api.team.create_team_from_signup.unavailable.app_error", nil, "d="+teamSignup.Team.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := ldap.GetUser(teamSignup.User.Username)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ldap.CheckPassword(teamSignup.User.Username, teamSignup.User.Password)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Team().Save(&teamSignup.Team); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
rteam := result.Data.(*model.Team)
|
||||||
|
|
||||||
|
if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
|
||||||
|
c.Err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.TeamId = rteam.Id
|
||||||
|
ruser, err := CreateUser(rteam, user)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamSignup.Team = *rteam
|
||||||
|
teamSignup.User = *ruser
|
||||||
|
|
||||||
|
w.Write([]byte(teamSignup.ToJson()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
team := model.TeamFromJson(r.Body)
|
team := model.TeamFromJson(r.Body)
|
||||||
rteam := CreateTeam(c, team)
|
rteam := CreateTeam(c, team)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type LdapInterface interface {
|
type LdapInterface interface {
|
||||||
DoLogin(team *model.Team, id string, password string) (*model.User, *model.AppError)
|
DoLogin(team *model.Team, id string, password string) (*model.User, *model.AppError)
|
||||||
|
GetUser(id string) (*model.User, *model.AppError)
|
||||||
|
CheckPassword(id string, password string) *model.AppError
|
||||||
}
|
}
|
||||||
|
|
||||||
var theLdapInterface LdapInterface
|
var theLdapInterface LdapInterface
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import ChoosePage from './team_signup_choose_auth.jsx';
|
import ChoosePage from './team_signup_choose_auth.jsx';
|
||||||
import EmailSignUpPage from './team_signup_with_email.jsx';
|
import EmailSignUpPage from './team_signup_with_email.jsx';
|
||||||
import SSOSignupPage from './team_signup_with_sso.jsx';
|
import SSOSignupPage from './team_signup_with_sso.jsx';
|
||||||
|
import LdapSignUpPage from './team_signup_with_ldap.jsx';
|
||||||
import Constants from '../utils/constants.jsx';
|
import Constants from '../utils/constants.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'mm-intl';
|
import {FormattedMessage} from 'mm-intl';
|
||||||
@@ -24,12 +25,18 @@ export default class TeamSignUp extends React.Component {
|
|||||||
count = count + 1;
|
count = count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (global.window.mm_config.EnableLdap === 'true') {
|
||||||
|
count = count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
this.state = {page: 'choose'};
|
this.state = {page: 'choose'};
|
||||||
} else if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
} else if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
this.state = {page: 'email'};
|
this.state = {page: 'email'};
|
||||||
} else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
} else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
this.state = {page: 'gitlab'};
|
this.state = {page: 'gitlab'};
|
||||||
|
} else if (global.window.mm_config.EnableLdap === 'true') {
|
||||||
|
this.state = {page: 'ldap'};
|
||||||
} else {
|
} else {
|
||||||
this.state = {page: 'none'};
|
this.state = {page: 'none'};
|
||||||
}
|
}
|
||||||
@@ -133,6 +140,13 @@ export default class TeamSignUp extends React.Component {
|
|||||||
<EmailSignUpPage/>
|
<EmailSignUpPage/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (this.state.page === 'ldap') {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{teamListing}
|
||||||
|
<LdapSignUpPage/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else if (this.state.page === 'gitlab') {
|
} else if (this.state.page === 'gitlab') {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -58,6 +58,30 @@ export default class ChooseAuthPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (global.window.mm_config.EnableLdap === 'true') {
|
||||||
|
buttons.push(
|
||||||
|
<a
|
||||||
|
className='btn btn-custom-login ldap btn-full'
|
||||||
|
key='ldap'
|
||||||
|
href='#'
|
||||||
|
onClick={
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.updatePage('ldap');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className='icon'/>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='choose_auth_page.ldapCreate'
|
||||||
|
defaultMessage='Create new team with LDAP Account'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<a
|
<a
|
||||||
|
|||||||
234
web/react/components/team_signup_with_ldap.jsx
Обычный файл
234
web/react/components/team_signup_with_ldap.jsx
Обычный файл
@@ -0,0 +1,234 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import * as utils from '../utils/utils.jsx';
|
||||||
|
import * as Client from '../utils/client.jsx';
|
||||||
|
|
||||||
|
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
|
||||||
|
|
||||||
|
const holders = defineMessages({
|
||||||
|
team_error: {
|
||||||
|
id: 'ldap_signup.team_error',
|
||||||
|
defaultMessage: 'Please enter a team name'
|
||||||
|
},
|
||||||
|
length_error: {
|
||||||
|
id: 'ldap_signup.length_error',
|
||||||
|
defaultMessage: 'Name must be 3 or more characters up to a maximum of 15'
|
||||||
|
},
|
||||||
|
teamName: {
|
||||||
|
id: 'ldap_signup.teamName',
|
||||||
|
defaultMessage: 'Enter name of new team'
|
||||||
|
},
|
||||||
|
badTeam: {
|
||||||
|
id: 'login_ldap.badTeam',
|
||||||
|
defaultMessage: 'Bad team name'
|
||||||
|
},
|
||||||
|
idReq: {
|
||||||
|
id: 'login_ldap.idlReq',
|
||||||
|
defaultMessage: 'An LDAP ID is required'
|
||||||
|
},
|
||||||
|
pwdReq: {
|
||||||
|
id: 'login_ldap.pwdReq',
|
||||||
|
defaultMessage: 'An LDAP password is required'
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
id: 'login_ldap.username',
|
||||||
|
defaultMessage: 'LDAP Username'
|
||||||
|
},
|
||||||
|
pwd: {
|
||||||
|
id: 'login_ldap.pwd',
|
||||||
|
defaultMessage: 'LDAP Password'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class LdapSignUpPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.valueChange = this.valueChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {name: '', id: '', password: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
|
var teamSignup = {};
|
||||||
|
teamSignup.user = {};
|
||||||
|
teamSignup.team = {};
|
||||||
|
var state = this.state;
|
||||||
|
state.serverError = null;
|
||||||
|
|
||||||
|
teamSignup.team.display_name = this.state.name;
|
||||||
|
|
||||||
|
if (!teamSignup.team.display_name) {
|
||||||
|
state.serverError = formatMessage(holders.team_error);
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamSignup.team.display_name.length <= 2) {
|
||||||
|
state.serverError = formatMessage(holders.length_error);
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = this.refs.id.value.trim();
|
||||||
|
if (!id) {
|
||||||
|
state.serverError = formatMessage(holders.idReq);
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = this.refs.password.value.trim();
|
||||||
|
if (!password) {
|
||||||
|
state.serverError = formatMessage(holders.pwdReq);
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.serverError = '';
|
||||||
|
this.setState(state);
|
||||||
|
|
||||||
|
teamSignup.team.name = utils.cleanUpUrlable(teamSignup.team.display_name);
|
||||||
|
teamSignup.team.type = 'O';
|
||||||
|
|
||||||
|
teamSignup.user.username = ReactDOM.findDOMNode(this.refs.id).value.trim();
|
||||||
|
teamSignup.user.password = ReactDOM.findDOMNode(this.refs.password).value.trim();
|
||||||
|
teamSignup.user.allow_marketing = true;
|
||||||
|
teamSignup.user.ldap = true;
|
||||||
|
teamSignup.user.auth_service = 'ldap';
|
||||||
|
|
||||||
|
$('#ldap-button').button('loading');
|
||||||
|
|
||||||
|
Client.createTeamWithLdap(teamSignup,
|
||||||
|
() => {
|
||||||
|
Client.track('signup', 'signup_team_ldap_complete');
|
||||||
|
|
||||||
|
Client.loginByLdap(teamSignup.team.name, id, password,
|
||||||
|
() => {
|
||||||
|
window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
$('#ldap-button').button('reset');
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
$('#ldap-button').button('reset');
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueChange() {
|
||||||
|
this.setState({
|
||||||
|
name: ReactDOM.findDOMNode(this.refs.teamname).value.trim(),
|
||||||
|
id: ReactDOM.findDOMNode(this.refs.id).value.trim(),
|
||||||
|
password: ReactDOM.findDOMNode(this.refs.password).value.trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
|
var nameError = null;
|
||||||
|
var nameDivClass = 'form-group';
|
||||||
|
if (this.state.nameError) {
|
||||||
|
nameError = <label className='control-label'>{this.state.nameError}</label>;
|
||||||
|
nameDivClass += ' has-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverError = null;
|
||||||
|
if (this.state.serverError) {
|
||||||
|
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
var disabled = false;
|
||||||
|
if (this.state.name.length <= 2) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.id.length <= 1) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.password.length <= 1) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
role='form'
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
>
|
||||||
|
<div className={nameDivClass}>
|
||||||
|
<input
|
||||||
|
autoFocus={true}
|
||||||
|
type='text'
|
||||||
|
ref='teamname'
|
||||||
|
className='form-control'
|
||||||
|
placeholder={this.props.intl.formatMessage(holders.teamName)}
|
||||||
|
maxLength='128'
|
||||||
|
onChange={this.valueChange}
|
||||||
|
spellCheck='false'
|
||||||
|
/>
|
||||||
|
{nameError}
|
||||||
|
</div>
|
||||||
|
<div className={nameDivClass}>
|
||||||
|
<input
|
||||||
|
className='form-control'
|
||||||
|
ref='id'
|
||||||
|
placeholder={formatMessage(holders.username)}
|
||||||
|
spellCheck='false'
|
||||||
|
onChange={this.valueChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={nameDivClass}>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
className='form-control'
|
||||||
|
ref='password'
|
||||||
|
placeholder={formatMessage(holders.pwd)}
|
||||||
|
spellCheck='false'
|
||||||
|
onChange={this.valueChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-group'>
|
||||||
|
<a
|
||||||
|
className='btn btn-custom-login ldap btn-full'
|
||||||
|
key='ldap'
|
||||||
|
id='ldap-button'
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleSubmit}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<span className='icon'/>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ldap_signup.ldap'
|
||||||
|
defaultMessage='Create team with LDAP Account'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{serverError}
|
||||||
|
</div>
|
||||||
|
<div className='form-group margin--extra-2x'>
|
||||||
|
<span><a href='/find_team'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ldap_signup.find'
|
||||||
|
defaultMessage='Find my teams'
|
||||||
|
/>
|
||||||
|
</a></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LdapSignUpPage.propTypes = {
|
||||||
|
intl: intlShape.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(LdapSignUpPage);
|
||||||
@@ -87,6 +87,21 @@ export function createTeamFromSignup(teamSignup, success, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createTeamWithLdap(teamSignup, success, error) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/v1/teams/create_with_ldap',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(teamSignup),
|
||||||
|
success,
|
||||||
|
error: function onError(xhr, status, err) {
|
||||||
|
var e = handleError('createTeamFromSignup', xhr, status, err);
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createTeamWithSSO(team, service, success, error) {
|
export function createTeamWithSSO(team, service, success, error) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/v1/teams/create_with_sso/' + service,
|
url: '/api/v1/teams/create_with_sso/' + service,
|
||||||
|
|||||||
@@ -214,6 +214,15 @@
|
|||||||
background-image: url("../images/googleLogo.png");
|
background-image: url("../images/googleLogo.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.ldap {
|
||||||
|
background: #dd4b39;
|
||||||
|
&:hover {
|
||||||
|
background: darken(#dd4b39, 10%);
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.email {
|
&.email {
|
||||||
background: #2389D7;
|
background: #2389D7;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -593,6 +593,7 @@
|
|||||||
"choose_auth_page.find": "Find my teams",
|
"choose_auth_page.find": "Find my teams",
|
||||||
"choose_auth_page.gitlabCreate": "Create new team with GitLab Account",
|
"choose_auth_page.gitlabCreate": "Create new team with GitLab Account",
|
||||||
"choose_auth_page.googleCreate": "Create new team with Google Apps Account",
|
"choose_auth_page.googleCreate": "Create new team with Google Apps Account",
|
||||||
|
"choose_auth_page.ldapCreate": "Create new team with LDAP Account",
|
||||||
"choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.",
|
"choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.",
|
||||||
"claim.account.noEmail": "No email specified",
|
"claim.account.noEmail": "No email specified",
|
||||||
"claim.email_to_sso.enterPwd": "Enter the password for your {team} {site} account",
|
"claim.email_to_sso.enterPwd": "Enter the password for your {team} {site} account",
|
||||||
@@ -964,6 +965,11 @@
|
|||||||
"sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15",
|
"sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15",
|
||||||
"sso_signup.teamName": "Enter name of new team",
|
"sso_signup.teamName": "Enter name of new team",
|
||||||
"sso_signup.team_error": "Please enter a team name",
|
"sso_signup.team_error": "Please enter a team name",
|
||||||
|
"ldap_signup.find": "Find my teams",
|
||||||
|
"ldap_signup.ldap": "Create team with LDAP Account",
|
||||||
|
"ldap_signup.length_error": "Name must be 3 or more characters up to a maximum of 15",
|
||||||
|
"ldap_signup.teamName": "Enter name of new team",
|
||||||
|
"ldap_signup.team_error": "Please enter a team name",
|
||||||
"suggestion.mention.all": "Notifies everyone in the team",
|
"suggestion.mention.all": "Notifies everyone in the team",
|
||||||
"suggestion.mention.channel": "Notifies everyone in the channel",
|
"suggestion.mention.channel": "Notifies everyone in the channel",
|
||||||
"suggestion.search.private": "Public Groups",
|
"suggestion.search.private": "Public Groups",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user