Этот коммит содержится в:
Christopher Speller
2017-09-06 23:04:13 -07:00
родитель 7bc8e9a08d
Коммит d8bd57901e
2396 изменённых файлов: 0 добавлений и 285726 удалений

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

@@ -1,7 +0,0 @@
{
"presets": [
"es2015",
"react",
"stage-0"
]
}

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

@@ -1,273 +0,0 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"impliedStrict": true,
"modules": true,
"experimentalObjectRestSpread": true
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true
},
"globals": {
"jest": true,
"describe": true,
"it": true,
"expect": true,
"before": true,
"after": true
},
"rules": {
"array-bracket-spacing": [2, "never"],
"array-callback-return": 2,
"arrow-body-style": 0,
"arrow-parens": [2, "always"],
"arrow-spacing": [2, { "before": true, "after": true }],
"block-scoped-var": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
"camelcase": [2, {"properties": "never"}],
"capitalized-comments": 0,
"class-methods-use-this": 1,
"comma-dangle": [2, "never"],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"complexity": [1, 10],
"computed-property-spacing": [2, "never"],
"consistent-return": 2,
"consistent-this": [2, "self"],
"constructor-super": 2,
"curly": [2, "all"],
"dot-location": [2, "object"],
"dot-notation": 2,
"eqeqeq": [2, "smart"],
"func-call-spacing": [2, "never"],
"func-name-matching": 0,
"func-names": 2,
"func-style": [2, "declaration"],
"generator-star-spacing": [2, {"before": false, "after": true}],
"global-require": 2,
"guard-for-in": 2,
"id-blacklist": 0,
"indent": [2, 4, {"SwitchCase": 0}],
"jsx-quotes": [2, "prefer-single"],
"key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "strict"}],
"keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}],
"line-comment-position": 0,
"linebreak-style": 2,
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"max-lines": [1, {"max": 450, "skipBlankLines": true, "skipComments": false}],
"max-nested-callbacks": [1, {"max":1}],
"max-nested-callbacks": [2, {"max":2}],
"max-statements-per-line": [2, {"max": 1}],
"multiline-ternary": [1, "never"],
"new-cap": 2,
"new-parens": 2,
"newline-before-return": 0,
"newline-per-chained-call": 0,
"no-alert": 2,
"no-array-constructor": 2,
"no-await-in-loop": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-class-assign": 2,
"no-compare-neg-zero": 2,
"no-cond-assign": [2, "except-parens"],
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-constant-condition": 2,
"no-debugger": 2,
"no-div-regex": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": [2, {"includeExports": true}],
"no-else-return": 2,
"no-empty": 2,
"no-empty-function": 2,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-label": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 0,
"no-implied-eval": 2,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-magic-numbers": [1, { "ignore": [-1, 0, 1, 2], "enforceConst": true, "detectObjects": true } ],
"no-mixed-operators": [2, {"allowSamePrecedence": false}],
"no-mixed-spaces-and-tabs": 2,
"no-multi-assign": 2,
"no-multi-spaces": [2, { "exceptions": { "Property": false } }],
"no-multi-str": 0,
"no-multiple-empty-lines": [2, {"max": 1}],
"no-native-reassign": 2,
"no-negated-condition": 2,
"no-nested-ternary": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-symbol": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-param-reassign": 2,
"no-process-env": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-return-assign": [2, "always"],
"no-return-await": 2,
"no-script-url": 2,
"no-self-assign": [2, {"props": true}],
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": [2, {"hoist": "functions"}],
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-tabs": 0,
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef-init": 2,
"no-undefined": 2,
"no-underscore-dangle": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unneeded-ternary": [2, {"defaultAssignment": false}],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-use-before-define": [2, "nofunc"],
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-useless-rename": 2,
"no-useless-return": 2,
"no-var": 0,
"no-void": 2,
"no-warning-comments": 1,
"no-whitespace-before-property": 2,
"no-with": 2,
"object-curly-newline": 0,
"object-curly-spacing": [2, "never"],
"object-property-newline": [2, {"allowMultiplePropertiesPerLine": true}],
"object-shorthand": [2, "always"],
"one-var": [2, "never"],
"one-var-declaration-per-line": 0,
"operator-assignment": [2, "always"],
"operator-linebreak": [2, "after"],
"padded-blocks": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-destructuring": 0,
"prefer-numeric-literals": 2,
"prefer-promise-reject-errors": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 0,
"quote-props": [2, "as-needed"],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"react/display-name": [2, { "ignoreTranspilerName": false }],
"react/forbid-component-props": 0,
"react/forbid-elements": [2, { "forbid": ["embed"] }],
"react/jsx-boolean-value": [2, "always"],
"react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }],
"react/jsx-curly-spacing": [2, "never"],
"react/jsx-equals-spacing": [2, "never"],
"react/jsx-filename-extension": 2,
"react/jsx-first-prop-new-line": [2, "multiline"],
"react/jsx-handler-names": 0,
"react/jsx-indent": [2, 4],
"react/jsx-indent-props": [2, 4],
"react/jsx-key": 2,
"react/jsx-max-props-per-line": [2, { "maximum": 1 }],
"react/jsx-no-bind": 0,
"react/jsx-no-comment-textnodes": 2,
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-literals": 2,
"react/jsx-no-target-blank": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-space-before-closing": [2, "never"],
"react/jsx-tag-spacing": [2, { "closingSlash": "never", "beforeSelfClosing": "never", "afterOpening": "never" }],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/jsx-wrap-multilines": 2,
"react/no-array-index-key": 1,
"react/no-children-prop": 2,
"react/no-danger": 0,
"react/no-danger-with-children": 2,
"react/no-deprecated": 2,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-find-dom-node": 1,
"react/no-is-mounted": 2,
"react/no-multi-comp": [2, { "ignoreStateless": true }],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
"react/no-unused-prop-types": [1, {"skipShapeProps": true}],
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 0,
"react/prop-types": 2,
"react/require-default-props": 0,
"react/require-optimization": 1,
"react/require-render-return": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/style-prop-object": 2,
"require-yield": 2,
"rest-spread-spacing": [2, "never"],
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"sort-imports": 0,
"sort-keys": 0,
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "never", "named": "never", "asyncArrow": "always"}],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"symbol-description": 2,
"template-curly-spacing": [2, "never"],
"valid-typeof": [2, {"requireStringLiterals": false}],
"vars-on-top": 0,
"wrap-iife": [2, "outside"],
"wrap-regex": 2,
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}]
}
}

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

@@ -1,59 +0,0 @@
.PHONY: build test run clean stop check-style run-unit emojis
BUILD_SERVER_DIR = ..
check-style: .yarninstall
@echo Checking for style guide compliance
yarn run check
test: .yarninstall
cd $(BUILD_SERVER_DIR) && $(MAKE) internal-test-web-client
.yarninstall: package.json
@echo Getting dependencies using yarn
yarn install
touch $@
build: .yarninstall
@echo Building mattermost Webapp
rm -rf dist
yarn run build
run: .yarninstall
@echo Running mattermost Webapp for development
yarn run run &
run-fullmap: .yarninstall
@echo FULL SOURCE MAP Running mattermost Webapp for development FULL SOURCE MAP
yarn run run-fullmap &
stop:
@echo Stopping changes watching
ifeq ($(OS),Windows_NT)
wmic process where "Caption='node.exe' and CommandLine like '%webpack%'" call terminate
else
@for PROCID in $$(ps -ef | grep "[n]ode.*[w]ebpack" | awk '{ print $$2 }'); do \
echo stopping webpack watch $$PROCID; \
kill $$PROCID; \
done
endif
clean:
@echo Cleaning Webapp
yarn cache clean
rm -rf dist
rm -rf node_modules
rm -f .yarninstall
emojis:
./make-emojis

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

@@ -1,397 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {clientLogout} from 'actions/global_actions.jsx';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import * as AdminActions from 'mattermost-redux/actions/admin';
import * as UserActions from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
export function saveConfig(config, success, error) {
AdminActions.updateConfig(config)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.updateConfig.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function reloadConfig(success, error) {
AdminActions.reloadConfig()(dispatch, getState).then(
(data) => {
if (data && success) {
AdminActions.getConfig()(dispatch, getState);
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.reloadConfig.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function adminResetMfa(userId, success, error) {
UserActions.updateUserMfa(userId, false)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getClusterStatus(success, error) {
AdminActions.getClusterStatus()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.getClusterStatus.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function testEmail(config, success, error) {
AdminActions.testEmail(config)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.testEmail.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function ldapTest(success, error) {
AdminActions.testLdap()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.testLdap.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function invalidateAllCaches(success, error) {
AdminActions.invalidateCaches()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.invalidateCaches.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function recycleDatabaseConnection(success, error) {
AdminActions.recycleDatabase()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.recycleDatabase.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function adminResetPassword(userId, password, success, error) {
UserActions.updateUserPassword(userId, '', password)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function samlCertificateStatus(success, error) {
AdminActions.getSamlCertificateStatus()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.getSamlCertificateStatus.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function ldapSyncNow(success, error) {
AdminActions.syncLdap()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.syncLdap.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getOAuthAppInfo(clientId, success, error) {
Client4.getOAuthAppInfo(clientId).then(
(data) => {
if (success) {
success(data);
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function allowOAuth2(params, success, error) {
const responseType = params.response_type;
const clientId = params.client_id;
const redirectUri = params.redirect_uri;
const state = params.state;
const scope = params.scope;
Client4.authorizeOAuthApp(responseType, clientId, redirectUri, state, scope).then(
(data) => {
if (success) {
success(data);
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function emailToLdap(loginId, password, token, ldapId, ldapPassword, success, error) {
UserActions.switchEmailToLdap(loginId, password, ldapId, ldapPassword, token)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.switchLogin.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function emailToOAuth(loginId, password, token, newType, success, error) {
UserActions.switchEmailToOAuth(newType, loginId, password, token)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.switchLogin.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function oauthToEmail(currentService, email, password, success, error) {
UserActions.switchOAuthToEmail(currentService, email, password)(dispatch, getState).then(
(data) => {
if (data) {
if (data.follow_link) {
clientLogout(data.follow_link);
}
if (success) {
success(data);
}
} else if (data == null && error) {
const serverError = getState().requests.users.switchLogin.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function uploadBrandImage(brandImage, success, error) {
AdminActions.uploadBrandImage(brandImage)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadBrandImage.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function uploadLicenseFile(file, success, error) {
AdminActions.uploadLicense(file)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadLicense.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removeLicenseFile(success, error) {
AdminActions.removeLicense()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removeLicense.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function uploadPublicSamlCertificate(file, success, error) {
AdminActions.uploadPublicSamlCertificate(file)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadPublicSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function uploadPrivateSamlCertificate(file, success, error) {
AdminActions.uploadPrivateSamlCertificate(file)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadPrivateSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function uploadIdpSamlCertificate(file, success, error) {
AdminActions.uploadIdpSamlCertificate(file)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadIdpSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removePublicSamlCertificate(success, error) {
AdminActions.removePublicSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removePublicSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removePrivateSamlCertificate(success, error) {
AdminActions.removePrivateSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removePrivateSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removeIdpSamlCertificate(success, error) {
AdminActions.removeIdpSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removeIdpSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getStandardAnalytics(teamId) {
AdminActions.getStandardAnalytics(teamId)(dispatch, getState);
}
export function getAdvancedAnalytics(teamId) {
AdminActions.getAdvancedAnalytics(teamId)(dispatch, getState);
}
export function getPostsPerDayAnalytics(teamId) {
AdminActions.getPostsPerDayAnalytics(teamId)(dispatch, getState);
}
export function getUsersPerDayAnalytics(teamId) {
AdminActions.getUsersPerDayAnalytics(teamId)(dispatch, getState);
}
export function elasticsearchTest(config, success, error) {
AdminActions.testElasticsearch(config)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.testElasticsearch.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function elasticsearchPurgeIndexes(success, error) {
AdminActions.purgeElasticsearchIndexes()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.purgeElasticsearchIndexes.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,413 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as PostActions from 'actions/post_actions.jsx';
import {loadProfilesForSidebar, loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import {Constants, Preferences} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import * as ChannelActions from 'mattermost-redux/actions/channels';
import {savePreferences, deletePreferences} from 'mattermost-redux/actions/preferences';
import {Client4} from 'mattermost-redux/client';
import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
export function goToChannel(channel) {
if (channel.fake) {
const user = UserStore.getProfileByUsername(channel.display_name);
if (!user) {
return;
}
openDirectChannelToUser(
user.id,
() => {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
},
null
);
} else {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
}
}
export function executeCommand(message, args, success, error) {
let msg = message;
let cmdLength = msg.indexOf(' ');
if (cmdLength < 0) {
cmdLength = msg.length;
}
const cmd = msg.substring(0, cmdLength).toLowerCase();
msg = cmd + msg.substring(cmdLength, msg.length);
switch (cmd) {
case '/search':
PostActions.searchForTerm(msg.substring(cmdLength + 1, msg.length));
return;
case '/shortcuts':
if (UserAgent.isMobile()) {
const err = {message: Utils.localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
error(err);
return;
}
GlobalActions.showShortcutsModal();
return;
case '/leave': {
// /leave command not supported in reply threads.
if (args.channel_id && (args.root_id || args.parent_id)) {
GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.parent_id);
return;
}
const channel = ChannelStore.getCurrent();
if (channel.type === Constants.PRIVATE_CHANNEL) {
GlobalActions.showLeavePrivateChannelModal(channel);
return;
} else if (
channel.type === Constants.DM_CHANNEL ||
channel.type === Constants.GM_CHANNEL
) {
let name;
let category;
if (channel.type === Constants.DM_CHANNEL) {
name = Utils.getUserIdFromChannelName(channel);
category = Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW;
} else {
name = channel.id;
category = Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW;
}
const currentUserId = UserStore.getCurrentId();
savePreferences(currentUserId, [{category, name, user_id: currentUserId, value: 'false'}])(dispatch, getState);
if (ChannelUtils.isFavoriteChannel(channel)) {
unmarkFavorite(channel.id);
}
browserHistory.push(`${TeamStore.getCurrentTeamRelativeUrl()}/channels/town-square`);
return;
}
break;
}
case '/settings':
GlobalActions.showAccountSettingsModal();
return;
}
Client4.executeCommand(msg, args).then(success).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function setChannelAsRead(channelIdParam) {
const channelId = channelIdParam || ChannelStore.getCurrentId();
ChannelActions.viewChannel(channelId)(dispatch, getState);
ChannelStore.resetCounts([channelId]);
ChannelStore.emitChange();
if (channelId === ChannelStore.getCurrentId()) {
ChannelStore.emitLastViewed(Number.MAX_VALUE, false);
}
}
export function addUserToChannel(channelId, userId, success, error) {
ChannelActions.addChannelMember(channelId, userId)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.addChannelMember.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removeUserFromChannel(channelId, userId, success, error) {
ChannelActions.removeChannelMember(channelId, userId)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.removeChannelMember.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function makeUserChannelAdmin(channelId, userId, success, error) {
ChannelActions.updateChannelMemberRoles(channelId, userId, 'channel_user channel_admin')(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.updateChannelMember.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function makeUserChannelMember(channelId, userId, success, error) {
ChannelActions.updateChannelMemberRoles(channelId, userId, 'channel_user')(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.updateChannelMember.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function openDirectChannelToUser(userId, success, error) {
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), userId);
const channel = ChannelStore.getByName(channelName);
if (channel) {
trackEvent('api', 'api_channels_join_direct');
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
loadProfilesForSidebar();
const currentUserId = UserStore.getCurrentId();
savePreferences(currentUserId, [{user_id: currentUserId, category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: userId, value: 'true'}])(dispatch, getState);
if (success) {
success(channel, true);
}
return;
}
ChannelActions.createDirectChannel(UserStore.getCurrentId(), userId)(dispatch, getState).then(
(result) => {
loadProfilesForSidebar();
if (result.data && success) {
success(result.data, false);
} else if (result.error && error) {
error({id: result.error.server_error_id, ...result.error});
}
}
);
}
export function openGroupChannelToUsers(userIds, success, error) {
ChannelActions.createGroupChannel(userIds)(dispatch, getState).then(
(result) => {
loadProfilesForSidebar();
if (result.data && success) {
success(result.data, false);
} else if (result.error && error) {
browserHistory.push(TeamStore.getCurrentTeamUrl());
error({id: result.error.server_error_id, ...result.error});
}
}
);
}
export function markFavorite(channelId) {
trackEvent('api', 'api_channels_favorited');
const currentUserId = UserStore.getCurrentId();
savePreferences(currentUserId, [{user_id: currentUserId, category: Preferences.CATEGORY_FAVORITE_CHANNEL, name: channelId, value: 'true'}])(dispatch, getState);
}
export function unmarkFavorite(channelId) {
trackEvent('api', 'api_channels_unfavorited');
const currentUserId = UserStore.getCurrentId();
const pref = {
user_id: currentUserId,
category: Preferences.CATEGORY_FAVORITE_CHANNEL,
name: channelId
};
deletePreferences(currentUserId, [pref])(dispatch, getState);
}
export function loadChannelsForCurrentUser() {
ChannelActions.fetchMyChannelsAndMembers(TeamStore.getCurrentId())(dispatch, getState).then(
() => {
loadDMsAndGMsForUnreads();
}
);
}
export function loadDMsAndGMsForUnreads() {
const unreads = ChannelStore.getUnreadCounts();
for (const id in unreads) {
if (!unreads.hasOwnProperty(id)) {
continue;
}
if (unreads[id].msgs > 0 || unreads[id].mentions > 0) {
const channel = ChannelStore.get(id);
if (channel && channel.type === Constants.DM_CHANNEL) {
loadNewDMIfNeeded(channel.id);
} else if (channel && channel.type === Constants.GM_CHANNEL) {
loadNewGMIfNeeded(channel.id);
}
}
}
}
export async function joinChannel(channel, success, error) {
const {data, serverError} = await ChannelActions.joinChannel(UserStore.getCurrentId(), null, channel.id)(dispatch, getState);
if (data && success) {
success(data);
} else if (data == null && error) {
error({id: serverError.server_error_id, ...serverError});
}
}
export function updateChannel(channel, success, error) {
ChannelActions.updateChannel(channel)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.updateChannel.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function searchMoreChannels(term, success, error) {
ChannelActions.searchChannels(TeamStore.getCurrentId(), term)(dispatch, getState).then(
(data) => {
if (data && success) {
const myMembers = getMyChannelMemberships(getState());
const channels = data.filter((c) => !myMembers[c.id]);
success(channels);
} else if (data == null && error) {
const serverError = getState().requests.channels.getChannels.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function autocompleteChannels(term, success, error) {
ChannelActions.searchChannels(TeamStore.getCurrentId(), term)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.getChannels.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateChannelNotifyProps(data, options, success, error) {
ChannelActions.updateChannelNotifyProps(data.user_id, data.channel_id, Object.assign({}, data, options))(dispatch, getState).then(
(result) => {
if (result && success) {
success(result);
} else if (result == null && error) {
const serverError = getState().requests.channels.updateChannelNotifyProps.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function createChannel(channel, success, error) {
ChannelActions.createChannel(channel)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.createChannel.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateChannelPurpose(channelId, purpose, success, error) {
ChannelActions.patchChannel(channelId, {purpose})(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.updateChannel.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateChannelHeader(channelId, header, success, error) {
ChannelActions.patchChannel(channelId, {header})(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.updateChannel.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getChannelMembersForUserIds(channelId, userIds, success, error) {
ChannelActions.getChannelMembersByIds(channelId, userIds)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.channels.members.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function leaveChannel(channelId, success) {
ChannelActions.leaveChannel(channelId)(dispatch, getState).then(
() => {
if (ChannelUtils.isFavoriteChannelId(channelId)) {
unmarkFavorite(channelId);
}
const townsquare = ChannelStore.getByName('town-square');
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townsquare.name);
if (success) {
success();
}
}
);
}
export async function deleteChannel(channelId, success, error) {
const {data, serverError} = await ChannelActions.deleteChannel(channelId)(dispatch, getState);
if (data && success) {
success(data);
} else if (serverError && error) {
error({id: serverError.server_error_id, ...serverError});
}
}

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

@@ -1,24 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import UserStore from 'stores/user_store.jsx';
export function trackEvent(category, event, props) {
if (global.window && global.window.analytics) {
const properties = Object.assign({category, type: event, user_actual_id: UserStore.getCurrentId()}, props);
const options = {
context: {
ip: '0.0.0.0'
},
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
};
global.window.analytics.track('event', properties, options);
}
}

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

@@ -1,72 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import UserStore from 'stores/user_store.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import {ActionTypes} from 'utils/constants.jsx';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getProfilesByIds} from 'mattermost-redux/actions/users';
import * as EmojiActions from 'mattermost-redux/actions/emojis';
export async function loadEmoji(getProfiles = true) {
const data = await EmojiActions.getAllCustomEmojis()(dispatch, getState);
if (data && getProfiles) {
loadProfilesForEmoji(data);
}
}
function loadProfilesForEmoji(emojiList) {
const profilesToLoad = {};
for (let i = 0; i < emojiList.length; i++) {
const emoji = emojiList[i];
if (!UserStore.hasProfile(emoji.creator_id)) {
profilesToLoad[emoji.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
getProfilesByIds(list)(dispatch, getState);
}
export function addEmoji(emoji, image, success, error) {
EmojiActions.createCustomEmoji(emoji, image)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.emojis.createCustomEmoji.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function deleteEmoji(emojiId, success, error) {
EmojiActions.deleteCustomEmoji(emojiId)(dispatch, getState).then(
(data) => {
if (data) {
// Needed to remove recently used emoji
AppDispatcher.handleServerAction({
type: ActionTypes.REMOVED_CUSTOM_EMOJI,
id: emojiId
});
if (success) {
success(data);
}
} else if (data == null && error) {
const serverError = getState().requests.emojis.deleteCustomEmoji.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,93 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import request from 'superagent';
import store from 'stores/redux_store.jsx';
import * as Utils from 'utils/utils.jsx';
import {FileTypes} from 'mattermost-redux/action_types';
import {forceLogoutIfNecessary} from 'mattermost-redux/actions/helpers';
import {getLogErrorAction} from 'mattermost-redux/actions/errors';
import {Client4} from 'mattermost-redux/client';
export function uploadFile(file, name, channelId, clientId, successCallback, errorCallback) {
const {dispatch, getState} = store;
function handleResponse(err, res) {
if (err) {
let e;
if (res && res.body && res.body.id) {
e = res.body;
} else if (err.status === 0 || !err.status) {
e = {message: Utils.localizeMessage('channel_loader.connection_error', 'There appears to be a problem with your internet connection.')};
} else {
e = {message: Utils.localizeMessage('channel_loader.unknown_error', 'We received an unexpected status code from the server.') + ' (' + err.status + ')'};
}
forceLogoutIfNecessary(err, dispatch);
const failure = {
type: FileTypes.UPLOAD_FILES_FAILURE,
clientIds: [clientId],
channelId,
rootId: null,
error: err
};
dispatch(batchActions([failure, getLogErrorAction(err)]), getState);
if (errorCallback) {
errorCallback(e, err, res);
}
} else if (res) {
const data = res.body.file_infos.map((fileInfo, index) => {
return {
...fileInfo,
clientId: res.body.client_ids[index]
};
});
dispatch(batchActions([
{
type: FileTypes.RECEIVED_UPLOAD_FILES,
data,
channelId,
rootId: null
},
{
type: FileTypes.UPLOAD_FILES_SUCCESS
}
]), getState);
if (successCallback) {
successCallback(res.body, res);
}
}
}
dispatch({type: FileTypes.UPLOAD_FILES_REQUEST}, getState);
return request.
post(Client4.getFilesRoute()).
set(Client4.getOptions().headers).
attach('files', file, name).
field('channel_id', channelId).
field('client_ids', clientId).
accept('application/json').
end(handleResponse);
}
export async function getPublicLink(fileId, success) {
Client4.getFilePublicLink(fileId).then(
(data) => {
if (data && success) {
success(data.link);
}
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
}

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

@@ -1,626 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';
import {loadProfilesForSidebar, loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
import {stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
import * as WebsocketActions from 'actions/websocket_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import {ActionTypes, Constants, ErrorPageTypes} from 'utils/constants.jsx';
import EventTypes from 'utils/event_types.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as Utils from 'utils/utils.jsx';
import en from 'i18n/en.json';
import * as I18n from 'i18n/i18n.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {Client4} from 'mattermost-redux/client';
import {removeUserFromTeam} from 'mattermost-redux/actions/teams';
import {viewChannel, getChannelStats, getMyChannelMember, getChannelAndMyMember, createDirectChannel, joinChannel} from 'mattermost-redux/actions/channels';
import {getPostThread} from 'mattermost-redux/actions/posts';
export function emitChannelClickEvent(channel) {
function userVisitedFakeChannel(chan, success, fail) {
const currentUserId = UserStore.getCurrentId();
const otherUserId = Utils.getUserIdFromChannelName(chan);
createDirectChannel(currentUserId, otherUserId)(dispatch, getState).then(
(result) => {
const receivedChannel = result.data;
if (receivedChannel) {
success(receivedChannel);
} else {
fail();
}
}
);
}
function switchToChannel(chan) {
const channelMember = ChannelStore.getMyMember(chan.id);
const getMyChannelMemberPromise = getMyChannelMember(chan.id)(dispatch, getState);
const oldChannelId = ChannelStore.getCurrentId();
getMyChannelMemberPromise.then(() => {
getChannelStats(chan.id)(dispatch, getState);
viewChannel(chan.id, oldChannelId)(dispatch, getState);
// Mark previous and next channel as read
ChannelStore.resetCounts([chan.id, oldChannelId]);
reloadIfServerVersionChanged();
});
// Subtract mentions for the team
const {msgs, mentions} = ChannelStore.getUnreadCounts()[chan.id] || {msgs: 0, mentions: 0};
TeamStore.subtractUnread(chan.team_id, msgs, mentions);
BrowserStore.setGlobalItem(chan.team_id, chan.id);
loadProfilesForSidebar();
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: chan.name,
id: chan.id,
team_id: chan.team_id,
total_msg_count: chan.total_msg_count,
channelMember,
prev: oldChannelId
});
}
if (channel.fake) {
userVisitedFakeChannel(
channel,
(data) => {
switchToChannel(data);
},
() => {
browserHistory.push('/' + this.state.currentTeam.name);
}
);
} else {
switchToChannel(channel);
}
}
export async function doFocusPost(channelId, postId, data) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_FOCUSED_POST,
postId,
channelId,
post_list: data
});
dispatch({
type: ActionTypes.RECEIVED_FOCUSED_POST,
data: postId,
channelId
});
const member = getState().entities.channels.myMembers[channelId];
if (member == null) {
await joinChannel(UserStore.getCurrentId(), null, channelId)(dispatch, getState);
}
loadChannelsForCurrentUser();
getChannelStats(channelId)(dispatch, getState);
}
export function emitPostFocusEvent(postId, onSuccess) {
loadChannelsForCurrentUser();
getPostThread(postId)(dispatch, getState).then(
(data) => {
if (data) {
const channelId = data.posts[data.order[0]].channel_id;
const channel = ChannelStore.getChannelById(channelId);
if (channel && channel.type === Constants.DM_CHANNEL) {
loadNewDMIfNeeded(channel.id);
} else if (channel && channel.type === Constants.GM_CHANNEL) {
loadNewGMIfNeeded(channel.id);
}
doFocusPost(channelId, postId, data).then(() => {
if (onSuccess) {
onSuccess();
}
});
} else {
browserHistory.push('/error?type=' + ErrorPageTypes.PERMALINK_NOT_FOUND);
}
}
);
}
export function emitCloseRightHandSide() {
SearchStore.storeSearchResults(null, false, false);
SearchStore.emitSearchChange();
dispatch({
type: ActionTypes.SELECT_POST,
postId: ''
});
}
export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) {
getPostThread(post.id)(dispatch, getState).then(
() => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
postId: Utils.getRootId(post),
from_search: SearchStore.getSearchTerm(),
from_flagged_posts: SearchStore.getIsFlaggedPosts(),
from_pinned_posts: SearchStore.getIsPinnedPosts()
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: null,
is_mention_search: isMentionSearch
});
}
);
}
export function emitLeaveTeam() {
removeUserFromTeam(TeamStore.getCurrentId(), UserStore.getCurrentId())(dispatch, getState);
}
export function emitUserPostedEvent(post) {
AppDispatcher.handleServerAction({
type: ActionTypes.CREATE_POST,
post
});
}
export function emitUserCommentedEvent(post) {
AppDispatcher.handleServerAction({
type: ActionTypes.CREATE_COMMENT,
post
});
}
export function showAccountSettingsModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL,
value: true
});
}
export function showShortcutsModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_SHORTCUTS_MODAL,
value: true
});
}
export function showDeletePostModal(post, commentCount = 0) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_DELETE_POST_MODAL,
value: true,
post,
commentCount
});
}
export function showChannelHeaderUpdateModal(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL,
value: true,
channel
});
}
export function showChannelPurposeUpdateModal(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL,
value: true,
channel
});
}
export function showChannelNameUpdateModal(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_CHANNEL_NAME_UPDATE_MODAL,
value: true,
channel
});
}
export function showGetPostLinkModal(post) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_GET_POST_LINK_MODAL,
value: true,
post
});
}
export function showGetPublicLinkModal(fileId) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL,
value: true,
fileId
});
}
export function showGetTeamInviteLinkModal() {
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL,
value: true
});
}
export function showInviteMemberModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_INVITE_MEMBER_MODAL,
value: true
});
}
export function showLeaveTeamModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_LEAVE_TEAM_MODAL,
value: true
});
}
export function showLeavePrivateChannelModal(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_LEAVE_PRIVATE_CHANNEL_MODAL,
value: channel
});
}
export function emitSuggestionPretextChanged(suggestionId, pretext) {
AppDispatcher.handleViewAction({
type: ActionTypes.SUGGESTION_PRETEXT_CHANGED,
id: suggestionId,
pretext
});
}
export function emitSelectNextSuggestion(suggestionId) {
AppDispatcher.handleViewAction({
type: ActionTypes.SUGGESTION_SELECT_NEXT,
id: suggestionId
});
}
export function emitSelectPreviousSuggestion(suggestionId) {
AppDispatcher.handleViewAction({
type: ActionTypes.SUGGESTION_SELECT_PREVIOUS,
id: suggestionId
});
}
export function emitCompleteWordSuggestion(suggestionId, term = '') {
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.SUGGESTION_COMPLETE_WORD,
id: suggestionId,
term
});
}
export function emitClearSuggestions(suggestionId) {
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS,
id: suggestionId
});
}
export function emitPreferenceChangedEvent(preference) {
AppDispatcher.handleServerAction({
type: Constants.ActionTypes.RECEIVED_PREFERENCE,
preference
});
if (addedNewDmUser(preference)) {
loadProfilesForSidebar();
}
}
export function emitPreferencesChangedEvent(preferences) {
AppDispatcher.handleServerAction({
type: Constants.ActionTypes.RECEIVED_PREFERENCES,
preferences
});
if (preferences.findIndex(addedNewDmUser) !== -1) {
loadProfilesForSidebar();
}
}
function addedNewDmUser(preference) {
return preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW && preference.value === 'true';
}
export function emitPreferencesDeletedEvent(preferences) {
AppDispatcher.handleServerAction({
type: Constants.ActionTypes.DELETED_PREFERENCES,
preferences
});
}
export function sendEphemeralPost(message, channelId, parentId) {
const timestamp = Utils.getTimestamp();
const post = {
id: Utils.generateId(),
user_id: '0',
channel_id: channelId || ChannelStore.getCurrentId(),
message,
type: Constants.PostTypes.EPHEMERAL,
create_at: timestamp,
update_at: timestamp,
root_id: parentId,
parent_id: parentId,
props: {}
};
handleNewPost(post);
}
export function newLocalizationSelected(locale) {
const localeInfo = I18n.getLanguageInfo(locale);
if (locale === 'en' || !localeInfo) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_LOCALE,
locale,
translations: en
});
} else {
Client4.getTranslations(localeInfo.url).then(
(data, res) => {
let translations = data;
if (!data && res.text) {
translations = JSON.parse(res.text);
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_LOCALE,
locale,
translations
});
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
}
}
export function loadCurrentLocale() {
const user = UserStore.getCurrentUser();
if (user && user.locale) {
newLocalizationSelected(user.locale);
} else {
loadDefaultLocale();
}
}
export function loadDefaultLocale() {
let locale = global.window.mm_config.DefaultClientLocale;
if (!I18n.getLanguageInfo(locale)) {
locale = 'en';
}
return newLocalizationSelected(locale);
}
let lastTimeTypingSent = 0;
export function emitLocalUserTypingEvent(channelId, parentId) {
const t = Date.now();
const membersInChannel = ChannelStore.getStats(channelId).member_count;
if (global.mm_license.IsLicensed === 'true' && global.mm_config.ExperimentalTownSquareIsReadOnly === 'true') {
const channel = ChannelStore.getChannelById(channelId);
if (channel && ChannelStore.isDefault(channel)) {
return;
}
}
if (((t - lastTimeTypingSent) > global.window.mm_config.TimeBetweenUserTypingUpdatesMilliseconds) && membersInChannel < global.window.mm_config.MaxNotificationsPerChannel && global.window.mm_config.EnableUserTypingMessages === 'true') {
WebSocketClient.userTyping(channelId, parentId);
lastTimeTypingSent = t;
}
}
export function emitRemoteUserTypingEvent(channelId, userId, postParentId) {
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.USER_TYPING,
channelId,
userId,
postParentId
});
}
export function emitUserLoggedOutEvent(redirectTo = '/', shouldSignalLogout = true) {
Client4.logout().then(
() => {
if (shouldSignalLogout) {
BrowserStore.signalLogout();
}
clientLogout(redirectTo);
}
).catch(
() => {
browserHistory.push(redirectTo);
}
);
}
export function clientLogout(redirectTo = '/') {
BrowserStore.clear();
ErrorStore.clearLastError();
ChannelStore.clear();
stopPeriodicStatusUpdates();
WebsocketActions.close();
document.cookie = 'MMUSERID=;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.location.href = redirectTo;
}
export function emitSearchMentionsEvent(user) {
let terms = '';
if (user.notify_props) {
const termKeys = UserStore.getMentionKeys(user.id);
if (termKeys.indexOf('@channel') !== -1) {
termKeys[termKeys.indexOf('@channel')] = '';
}
if (termKeys.indexOf('@all') !== -1) {
termKeys[termKeys.indexOf('@all')] = '';
}
terms = termKeys.join(' ');
}
trackEvent('api', 'api_posts_search_mention');
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term: terms,
do_search: true,
is_mention_search: true
});
}
export function toggleSideBarAction(visible) {
if (!visible) {
//Array of actions resolving in the closing of the sidebar
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: null
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term: null,
do_search: false,
is_mention_search: false
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
postId: null
});
}
}
export function toggleSideBarRightMenuAction() {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: null
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
postId: null
});
document.querySelector('.app__body .inner-wrap').classList.remove('move--right', 'move--left', 'move--left-small');
document.querySelector('.app__body .sidebar--left').classList.remove('move--right');
document.querySelector('.app__body .sidebar--right').classList.remove('move--left');
document.querySelector('.app__body .sidebar--menu').classList.remove('move--left');
}
export function emitBrowserFocus(focus) {
AppDispatcher.handleViewAction({
type: ActionTypes.BROWSER_CHANGE_FOCUS,
focus
});
}
export function redirectUserToDefaultTeam() {
const teams = TeamStore.getAll();
const teamMembers = TeamStore.getMyTeamMembers();
let teamId = BrowserStore.getGlobalItem('team');
function redirect(teamName, channelName) {
browserHistory.push(`/${teamName}/channels/${channelName}`);
}
if (!teams[teamId] && teamMembers.length > 0) {
let myTeams = [];
for (const index in teamMembers) {
if (teamMembers.hasOwnProperty(index)) {
const teamMember = teamMembers[index];
myTeams.push(teams[teamMember.team_id]);
}
}
if (myTeams.length > 0) {
myTeams = myTeams.sort(sortTeamsByDisplayName);
teamId = myTeams[0].id;
}
}
if (teams[teamId]) {
const channelId = BrowserStore.getGlobalItem(teamId);
const channel = ChannelStore.getChannelById(channelId);
if (channel) {
redirect(teams[teamId].name, channel);
} else if (channelId) {
getChannelAndMyMember(channelId)(dispatch, getState).then(
(data) => {
if (data) {
redirect(teams[teamId].name, data.channel.name);
} else {
redirect(teams[teamId].name, 'town-square');
}
}
);
} else {
redirect(teams[teamId].name, 'town-square');
}
} else {
browserHistory.push('/select_team');
}
}
export function postListScrollChange(forceScrollToBottom = false) {
AppDispatcher.handleViewAction({
type: EventTypes.POST_LIST_SCROLL_CHANGE,
value: forceScrollToBottom
});
}
export function emitPopoverMentionKeyClick(isRHS, mentionKey) {
AppDispatcher.handleViewAction({
type: ActionTypes.POPOVER_MENTION_KEY_CLICK,
isRHS,
mentionKey
});
}
let serverVersion = '';
export function reloadIfServerVersionChanged() {
const newServerVersion = Client4.getServerVersion();
if (serverVersion && serverVersion !== newServerVersion) {
console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
window.location.reload(true);
}
serverVersion = newServerVersion;
}

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

@@ -1,301 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import {ActionTypes} from 'utils/constants.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {Client4} from 'mattermost-redux/client';
import {getProfilesByIds} from 'mattermost-redux/actions/users';
import * as IntegrationActions from 'mattermost-redux/actions/integrations';
import request from 'superagent';
export function loadIncomingHooks(complete) {
IntegrationActions.getIncomingHooks('', 0, 10000)(dispatch, getState).then(
(data) => {
if (data) {
loadProfilesForIncomingHooks(data);
}
if (complete) {
complete(data);
}
}
);
}
export function loadIncomingHooksForTeam(teamId, complete) {
IntegrationActions.getIncomingHooks(teamId, 0, 10000)(dispatch, getState).then(
(data) => {
if (data) {
loadProfilesForIncomingHooks(data);
}
if (complete) {
complete(data);
}
}
);
}
function loadProfilesForIncomingHooks(hooks) {
const profilesToLoad = {};
for (let i = 0; i < hooks.length; i++) {
const hook = hooks[i];
if (!UserStore.hasProfile(hook.user_id)) {
profilesToLoad[hook.user_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
getProfilesByIds(list)(dispatch, getState);
}
export function loadOutgoingHooks(complete) {
IntegrationActions.getOutgoingHooks('', '', 0, 10000)(dispatch, getState).then(
(data) => {
if (data) {
loadProfilesForOutgoingHooks(data);
}
if (complete) {
complete(data);
}
}
);
}
export function loadOutgoingHooksForTeam(teamId, complete) {
IntegrationActions.getOutgoingHooks('', teamId, 0, 10000)(dispatch, getState).then(
(data) => {
if (data) {
loadProfilesForOutgoingHooks(data);
}
if (complete) {
complete(data);
}
}
);
}
function loadProfilesForOutgoingHooks(hooks) {
const profilesToLoad = {};
for (let i = 0; i < hooks.length; i++) {
const hook = hooks[i];
if (!UserStore.hasProfile(hook.creator_id)) {
profilesToLoad[hook.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
getProfilesByIds(list)(dispatch, getState);
}
export function loadTeamCommands(complete) {
IntegrationActions.getCustomTeamCommands(TeamStore.getCurrentId())(dispatch, getState).then(
(data) => {
if (data) {
loadProfilesForCommands(data);
}
if (complete) {
complete(data);
}
}
);
}
function loadProfilesForCommands(commands) {
const profilesToLoad = {};
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
if (!UserStore.hasProfile(command.creator_id)) {
profilesToLoad[command.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
getProfilesByIds(list)(dispatch, getState);
}
export function addIncomingHook(hook, success, error) {
IntegrationActions.createIncomingHook(hook)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.createIncomingHook.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateIncomingHook(hook, success, error) {
IntegrationActions.updateIncomingHook(hook)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.updateIncomingHook.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function addOutgoingHook(hook, success, error) {
IntegrationActions.createOutgoingHook(hook)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.createOutgoingHook.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateOutgoingHook(hook, success, error) {
IntegrationActions.updateOutgoingHook(hook)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.updateOutgoingHook.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function deleteIncomingHook(id) {
IntegrationActions.removeIncomingHook(id)(dispatch, getState);
}
export function deleteOutgoingHook(id) {
IntegrationActions.removeOutgoingHook(id)(dispatch, getState);
}
export function regenOutgoingHookToken(id) {
IntegrationActions.regenOutgoingHookToken(id)(dispatch, getState);
}
export function addCommand(command, success, error) {
IntegrationActions.addCommand(command)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.addCommand.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function editCommand(command, success, error) {
IntegrationActions.editCommand(command)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.editCommand.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function deleteCommand(id) {
IntegrationActions.deleteCommand(id)(dispatch, getState);
}
export function regenCommandToken(id) {
IntegrationActions.regenCommandToken(id)(dispatch, getState);
}
export function getSuggestedCommands(command, suggestionId, component) {
Client4.getCommandsList(TeamStore.getCurrentId()).then(
(data) => {
let matches = [];
data.forEach((cmd) => {
if (!cmd.auto_complete) {
return;
}
if (cmd.trigger !== 'shortcuts' || !UserAgent.isMobile()) {
if (('/' + cmd.trigger).indexOf(command) === 0) {
const s = '/' + cmd.trigger;
let hint = '';
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
hint = cmd.auto_complete_hint;
}
matches.push({
suggestion: s,
hint,
description: cmd.auto_complete_desc
});
}
}
});
matches = matches.sort((a, b) => a.suggestion.localeCompare(b.suggestion));
// pull out the suggested commands from the returned data
const terms = matches.map((suggestion) => suggestion.suggestion);
if (terms.length > 0) {
AppDispatcher.handleServerAction({
type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
id: suggestionId,
matchedPretext: command,
terms,
items: matches,
component
});
}
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
}
export function getYoutubeVideoInfo(googleKey, videoId, success, error) {
request.get('https://www.googleapis.com/youtube/v3/videos').
query({part: 'snippet', id: videoId, key: googleKey}).
end((err, res) => {
if (err) {
return error(err);
}
if (!res.body) {
console.error('Missing response body for getYoutubeVideoInfo'); // eslint-disable-line no-console
}
return success(res.body);
});
}

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

@@ -1,34 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as JobsActions from 'mattermost-redux/actions/jobs';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
export function createJob(job, success, error) {
JobsActions.createJob(job)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.jobs.createJob.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function cancelJob(jobId, success, error) {
JobsActions.cancelJob(jobId)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.jobs.cancelJob.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,132 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import NotificationStore from 'stores/notification_store.jsx';
import {isSystemMessage} from 'utils/post_utils.jsx';
import {buildGroupChannelName} from 'utils/channel_utils.jsx';
import {isWindowsApp, isMacApp, isMobileApp} from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
export function sendDesktopNotification(post, msgProps) {
if ((UserStore.getCurrentId() === post.user_id && post.props.from_webhook !== 'true')) {
return;
}
if (isSystemMessage(post)) {
return;
}
let mentions = [];
if (msgProps.mentions) {
mentions = JSON.parse(msgProps.mentions);
}
const teamId = msgProps.team_id;
let channel = ChannelStore.get(post.channel_id);
const user = UserStore.getCurrentUser();
const member = ChannelStore.getMyMember(post.channel_id);
let notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
if (notifyLevel === 'default') {
notifyLevel = user.notify_props.desktop;
}
if (notifyLevel === 'none') {
return;
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) {
return;
}
let username = Utils.localizeMessage('channel_loader.someone', 'Someone');
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
username = post.props.override_username;
} else if (msgProps.sender_name) {
username = msgProps.sender_name;
} else if (UserStore.hasProfile(post.user_id)) {
username = UserStore.getProfile(post.user_id).username;
}
let title = Utils.localizeMessage('channel_loader.posted', 'Posted');
if (!channel) {
title = msgProps.channel_display_name;
channel = {
name: msgProps.channel_name,
type: msgProps.channel_type
};
} else if (channel.type === Constants.DM_CHANNEL) {
title = Utils.localizeMessage('notification.dm', 'Direct Message');
} else if (channel.type === Constants.GM_CHANNEL) {
title = buildGroupChannelName(channel.id);
} else {
title = channel.display_name;
}
if (title === '') {
if (msgProps.channel_type === Constants.DM_CHANNEL) {
title = Utils.localizeMessage('notification.dm', 'Direct Message');
} else {
title = msgProps.channel_display_name;
}
}
let notifyText = post.message;
const msgPropsPost = JSON.parse(msgProps.post);
const attachments = msgPropsPost && msgPropsPost.props && msgPropsPost.props.attachments ? msgPropsPost.props.attachments : [];
let image = false;
attachments.forEach((attachment) => {
if (notifyText.length === 0) {
notifyText = attachment.fallback ||
attachment.pretext ||
attachment.text;
}
image |= attachment.image_url.length > 0;
});
notifyText = notifyText.replace(/\n+/g, ' ');
if (notifyText.length > 50) {
notifyText = notifyText.substring(0, 49) + '...';
}
let body = '';
if (notifyText.length === 0) {
if (msgProps.image) {
body = username + Utils.localizeMessage('channel_loader.uploadedImage', ' uploaded an image');
} else if (msgProps.otherFile) {
body = username + Utils.localizeMessage('channel_loader.uploadedFile', ' uploaded a file');
} else if (image) {
body = username + Utils.localizeMessage('channel_loader.postedImage', ' posted an image');
} else {
body = username + Utils.localizeMessage('channel_loader.something', ' did something new');
}
} else {
body = username + Utils.localizeMessage('channel_loader.wrote', ' wrote: ') + notifyText;
}
let duration = Constants.DEFAULT_NOTIFICATION_DURATION;
if (user.notify_props && user.notify_props.desktop_duration) {
duration = parseInt(user.notify_props.desktop_duration, 10) * 1000;
}
//Play a sound if explicitly set in settings
const sound = !user.notify_props || user.notify_props.desktop_sound === 'true';
// Notify if you're not looking in the right channel or when
// the window itself is not active
const activeChannel = ChannelStore.getCurrent();
const channelId = channel ? channel.id : null;
const notify = (activeChannel && activeChannel.id !== channelId) || !NotificationStore.getFocus();
if (notify) {
Utils.notifyMe(title, body, channel, teamId, duration, !sound);
//Don't add extra sounds on native desktop clients
if (sound && !isWindowsApp() && !isMacApp() && !isMobileApp()) {
Utils.ding();
}
}
}

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

@@ -1,21 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import * as IntegrationActions from 'mattermost-redux/actions/integrations';
export function deleteOAuthApp(id, success, error) {
IntegrationActions.deleteOAuthApp(id)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.integrations.deleteOAuthApp.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,357 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PostStore from 'stores/post_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
import {sendDesktopNotification} from 'actions/notification_actions.jsx';
import {ActionTypes, Constants} from 'utils/constants.jsx';
import {EMOJI_PATTERN} from 'utils/emoticons.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import * as PostActions from 'mattermost-redux/actions/posts';
import {getMyChannelMember} from 'mattermost-redux/actions/channels';
import {Client4} from 'mattermost-redux/client';
import {PostTypes} from 'mattermost-redux/action_types';
import * as Selectors from 'mattermost-redux/selectors/entities/posts';
import {batchActions} from 'redux-batched-actions';
export function handleNewPost(post, msg) {
let websocketMessageProps = {};
if (msg) {
websocketMessageProps = msg.data;
}
if (ChannelStore.getMyMember(post.channel_id)) {
completePostReceive(post, websocketMessageProps);
} else {
getMyChannelMember(post.channel_id)(dispatch, getState).then(() => completePostReceive(post, websocketMessageProps));
}
if (msg && msg.data) {
if (msg.data.channel_type === Constants.DM_CHANNEL) {
loadNewDMIfNeeded(post.channel_id);
} else if (msg.data.channel_type === Constants.GM_CHANNEL) {
loadNewGMIfNeeded(post.channel_id);
}
}
}
function completePostReceive(post, websocketMessageProps) {
if (post.root_id && Selectors.getPost(getState(), post.root_id) == null) {
PostActions.getPostThread(post.root_id)(dispatch, getState).then(
(data) => {
dispatchPostActions(post, websocketMessageProps);
PostActions.getProfilesAndStatusesForPosts(data.posts, dispatch, getState);
}
);
return;
}
dispatchPostActions(post, websocketMessageProps);
}
function dispatchPostActions(post, websocketMessageProps) {
const {currentChannelId} = getState().entities.channels;
if (post.channel_id === currentChannelId) {
dispatch({
type: ActionTypes.INCREASE_POST_VISIBILITY,
data: post.channel_id,
amount: 1
});
}
// Need manual dispatch to remove pending post
dispatch({
type: PostTypes.RECEIVED_POSTS,
data: {
order: [],
posts: {
[post.id]: post
}
},
channelId: post.channel_id
});
// Still needed to update unreads
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST,
post,
websocketMessageProps
});
sendDesktopNotification(post, websocketMessageProps);
}
export function flagPost(postId) {
PostActions.flagPost(postId)(dispatch, getState);
}
export function unflagPost(postId) {
PostActions.unflagPost(postId)(dispatch, getState);
}
export function getFlaggedPosts() {
Client4.getFlaggedPosts(UserStore.getCurrentId(), '', TeamStore.getCurrentId()).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term: null,
do_search: false,
is_mention_search: false
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: data,
is_flagged_posts: true,
is_pinned_posts: false
});
PostActions.getProfilesAndStatusesForPosts(data.posts, dispatch, getState);
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
}
export function getPinnedPosts(channelId = ChannelStore.getCurrentId()) {
Client4.getPinnedPosts(channelId).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term: null,
do_search: false,
is_mention_search: false
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: data,
is_flagged_posts: false,
is_pinned_posts: true
});
PostActions.getProfilesAndStatusesForPosts(data.posts, dispatch, getState);
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
}
export function addReaction(channelId, postId, emojiName) {
PostActions.addReaction(postId, emojiName)(dispatch, getState);
}
export function removeReaction(channelId, postId, emojiName) {
PostActions.removeReaction(postId, emojiName)(dispatch, getState);
}
export function createPost(post, files, success) {
// parse message and emit emoji event
const emojis = post.message.match(EMOJI_PATTERN);
if (emojis) {
for (const emoji of emojis) {
const trimmed = emoji.substring(1, emoji.length - 1);
emitEmojiPosted(trimmed);
}
}
PostActions.createPost(post, files)(dispatch, getState).then(() => {
if (post.root_id) {
PostStore.storeCommentDraft(post.root_id, null);
} else {
PostStore.storeDraft(post.channel_id, null);
}
if (success) {
success();
}
});
}
export function updatePost(post, success) {
PostActions.editPost(post)(dispatch, getState).then(
(data) => {
if (data && success) {
success();
} else {
const serverError = getState().requests.posts.editPost.error;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ERROR,
err: {id: serverError.server_error_id, ...serverError},
method: 'editPost'
});
}
}
);
}
export function emitEmojiPosted(emoji) {
AppDispatcher.handleServerAction({
type: ActionTypes.EMOJI_POSTED,
alias: emoji
});
}
export function deletePost(channelId, post, success) {
const {currentUserId} = getState().entities.users;
let hardDelete = false;
if (post.user_id === currentUserId) {
hardDelete = true;
}
PostActions.deletePost(post, hardDelete)(dispatch, getState).then(
() => {
if (post.id === getState().views.rhs.selectedPostId) {
dispatch({
type: ActionTypes.SELECT_POST,
postId: ''
});
}
dispatch({
type: PostTypes.REMOVE_POST,
data: post
});
// Needed for search store
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.REMOVE_POST,
post
});
const {focusedPostId} = getState().views.channel;
const channel = getState().entities.channels.channels[post.channel_id];
if (post.id === focusedPostId && channel) {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
}
if (success) {
success();
}
}
);
}
export function performSearch(terms, isMentionSearch, success, error) {
Client4.searchPosts(TeamStore.getCurrentId(), terms, isMentionSearch).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: data,
is_mention_search: isMentionSearch
});
PostActions.getProfilesAndStatusesForPosts(data.posts, dispatch, getState);
if (success) {
success(data);
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
const POST_INCREASE_AMOUNT = Constants.POST_CHUNK_SIZE / 2;
// Returns true if there are more posts to load
export function increasePostVisibility(channelId, focusedPostId) {
return async (doDispatch, doGetState) => {
if (doGetState().views.channel.loadingPosts[channelId]) {
return true;
}
const currentPostVisibility = doGetState().views.channel.postVisibility[channelId];
if (currentPostVisibility >= Constants.MAX_POST_VISIBILITY) {
return true;
}
doDispatch(batchActions([
{
type: ActionTypes.LOADING_POSTS,
data: true,
channelId
},
{
type: ActionTypes.INCREASE_POST_VISIBILITY,
data: channelId,
amount: POST_INCREASE_AMOUNT
}
]));
const page = Math.floor(currentPostVisibility / POST_INCREASE_AMOUNT);
let posts;
if (focusedPostId) {
posts = await PostActions.getPostsBefore(channelId, focusedPostId, page, POST_INCREASE_AMOUNT)(dispatch, getState);
} else {
posts = await PostActions.getPosts(channelId, page, POST_INCREASE_AMOUNT)(doDispatch, doGetState);
}
doDispatch({
type: ActionTypes.LOADING_POSTS,
data: false,
channelId
});
return posts.order.length >= POST_INCREASE_AMOUNT;
};
}
export function searchForTerm(term) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term,
do_search: true
});
}
export function pinPost(postId) {
return async (doDispatch, doGetState) => {
await PostActions.pinPost(postId)(doDispatch, doGetState);
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_PINNED,
postId
});
};
}
export function unpinPost(postId) {
return async (doDispatch, doGetState) => {
await PostActions.unpinPost(postId)(doDispatch, doGetState);
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_UNPINNED,
postId
});
};
}
export function doPostAction(postId, actionId) {
PostActions.doPostAction(postId, actionId)(dispatch, getState);
}

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

@@ -1,128 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {Preferences, Constants} from 'utils/constants.jsx';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getStatusesByIds} from 'mattermost-redux/actions/users';
export function loadStatusesForChannel(channelId = ChannelStore.getCurrentId()) {
const postList = PostStore.getVisiblePosts(channelId);
if (!postList || !postList.posts) {
return;
}
const statuses = UserStore.getStatuses();
const statusesToLoad = {};
for (const pid in postList.posts) {
if (!postList.posts.hasOwnProperty(pid)) {
continue;
}
const post = postList.posts[pid];
if (statuses[post.user_id] == null) {
statusesToLoad[post.user_id] = true;
}
}
loadStatusesByIds(Object.keys(statusesToLoad));
}
export function loadStatusesForDMSidebar() {
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
const statusesToLoad = [];
for (const [key, value] of dmPrefs) {
if (value === 'true') {
statusesToLoad.push(key);
}
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesForChannelAndSidebar() {
const statusesToLoad = {};
const channelId = ChannelStore.getCurrentId();
const posts = PostStore.getVisiblePosts(channelId) || [];
for (const post of posts) {
statusesToLoad[post.user_id] = true;
}
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
for (const [key, value] of dmPrefs) {
if (value === 'true') {
statusesToLoad[key] = true;
}
}
const {currentUserId} = getState().entities.users;
statusesToLoad[currentUserId] = true;
loadStatusesByIds(Object.keys(statusesToLoad));
}
export function loadStatusesForProfilesList(users) {
if (users == null) {
return;
}
const statusesToLoad = [];
for (let i = 0; i < users.length; i++) {
statusesToLoad.push(users[i].id);
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesForProfilesMap(users) {
if (users == null) {
return;
}
const statusesToLoad = [];
for (const userId in users) {
if (!users.hasOwnProperty(userId)) {
return;
}
statusesToLoad.push(userId);
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesByIds(userIds) {
if (userIds.length === 0) {
return;
}
getStatusesByIds(userIds)(dispatch, getState);
}
let intervalId = '';
export function startPeriodicStatusUpdates() {
clearInterval(intervalId);
intervalId = setInterval(
() => {
loadStatusesForChannelAndSidebar();
},
Constants.STATUS_INTERVAL
);
}
export function stopPeriodicStatusUpdates() {
clearInterval(intervalId);
}

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

@@ -1,200 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import TeamStore from 'stores/team_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {Client4} from 'mattermost-redux/client';
import {getUser} from 'mattermost-redux/actions/users';
import {viewChannel} from 'mattermost-redux/actions/channels';
import * as TeamActions from 'mattermost-redux/actions/teams';
import {TeamTypes} from 'mattermost-redux/action_types';
export function checkIfTeamExists(teamName, onSuccess, onError) {
TeamActions.checkIfTeamExists(teamName)(dispatch, getState).then(
(exists) => {
if (exists != null && onSuccess) {
onSuccess(exists);
} else if (exists == null && onError) {
const serverError = getState().requests.teams.getTeam.error;
onError({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function createTeam(team, onSuccess, onError) {
TeamActions.createTeam(team)(dispatch, getState).then(
(rteam) => {
if (rteam && onSuccess) {
browserHistory.push('/' + rteam.name + '/channels/town-square');
onSuccess(rteam);
} else if (rteam == null && onError) {
const serverError = getState().requests.teams.createTeam.error;
onError({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateTeam(team, onSuccess, onError) {
TeamActions.updateTeam(team)(dispatch, getState).then(
(rteam) => {
if (rteam && onSuccess) {
browserHistory.push('/' + rteam.name + '/channels/town-square');
onSuccess(rteam);
} else if (rteam == null && onError) {
const serverError = getState().requests.teams.updateTeam.error;
onError({id: serverError.server_error_id, ...serverError});
}
},
);
}
export function removeUserFromTeam(teamId, userId, success, error) {
TeamActions.removeUserFromTeam(teamId, userId)(dispatch, getState).then(
(data) => {
getUser(userId)(dispatch, getState);
TeamActions.getTeamStats(teamId)(dispatch, getState);
if (data && success) {
success();
} else if (data == null && error) {
const serverError = getState().requests.teams.removeUserFromTeam.error;
error({id: serverError.server_error_id, ...serverError});
}
},
);
}
export function updateTeamMemberRoles(teamId, userId, newRoles, success, error) {
TeamActions.updateTeamMemberRoles(teamId, userId, newRoles)(dispatch, getState).then(
(data) => {
if (data && success) {
success();
} else if (data == null && error) {
const serverError = getState().requests.teams.updateTeamMember.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function addUserToTeamFromInvite(data, hash, inviteId, success, error) {
Client4.addToTeamFromInvite(hash, data, inviteId).then(
(member) => {
TeamActions.getTeam(member.team_id)(dispatch, getState).then(
(team) => {
dispatch({
type: TeamTypes.RECEIVED_MY_TEAM_MEMBER,
data: {
...member,
delete_at: 0,
msg_count: 0,
mention_count: 0
}
});
if (success) {
success(team);
}
}
);
},
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function addUsersToTeam(teamId, userIds, success, error) {
TeamActions.addUsersToTeam(teamId, userIds)(dispatch, getState).then(
(teamMembers) => {
if (teamMembers && success) {
success(teamMembers);
} else if (teamMembers == null && error) {
const serverError = getState().requests.teams.addUserToTeam.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getInviteInfo(inviteId, success, error) {
Client4.getTeamInviteInfo(inviteId).then(
(inviteData) => {
if (success) {
success(inviteData);
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function inviteMembers(data, success, error) {
if (!data.invites) {
success();
}
const emails = [];
data.invites.forEach((i) => {
emails.push(i.email);
});
TeamActions.sendEmailInvitesToTeam(TeamStore.getCurrentId(), emails)(dispatch, getState).then(
(result) => {
if (result && success) {
success();
} else if (result == null && error) {
const serverError = getState().requests.teams.emailInvite.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function switchTeams(url) {
viewChannel(ChannelStore.getCurrentId())(dispatch, getState);
browserHistory.push(url);
}
export function getTeamsForUser(userId, success, error) {
TeamActions.getTeamsForUser(userId)(dispatch, getState).then(
(result) => {
if (result && success) {
success(result);
} else if (result == null && error) {
const serverError = getState().requests.teams.getTeams.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getTeamMembersForUser(userId, success, error) {
TeamActions.getTeamMembersForUser(userId)(dispatch, getState).then(
(result) => {
if (result && success) {
success(result);
} else if (result == null && error) {
const serverError = getState().requests.teams.getTeamMembers.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,839 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import PreferenceStore from 'stores/preference_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import {getChannelMembersForUserIds} from 'actions/channel_actions.jsx';
import {loadCurrentLocale, clientLogout} from 'actions/global_actions.jsx';
import {loadStatusesForProfilesList, loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
import {getDirectChannelName, getUserIdFromChannelName} from 'utils/utils.jsx';
import {Constants, ActionTypes, Preferences} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import * as Selectors from 'mattermost-redux/selectors/entities/users';
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
import * as UserActions from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
import {getTeamMembersByIds, getMyTeamMembers, getMyTeamUnreads} from 'mattermost-redux/actions/teams';
import {getChannelAndMyMember} from 'mattermost-redux/actions/channels';
import {savePreferences as savePreferencesRedux, deletePreferences} from 'mattermost-redux/actions/preferences';
import {Preferences as PreferencesRedux} from 'mattermost-redux/constants';
export async function loadMe() {
await UserActions.loadMe()(dispatch, getState);
if (window.mm_config) {
loadCurrentLocale();
}
}
export function loadMeAndConfig(callback) {
getClientConfig()(store.dispatch, store.getState).then((config) => {
global.window.mm_config = config;
if (global.window && global.window.analytics) {
global.window.analytics.identify(global.window.mm_config.DiagnosticId, {}, {
context: {
ip: '0.0.0.0'
},
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
});
}
Promise.all([
loadMe(),
getLicenseConfig()(store.dispatch, store.getState).then(
(license) => {
global.window.mm_license = license;
}
)
]).then(callback);
});
}
export function switchFromLdapToEmail(email, password, token, ldapPassword, success, error) {
UserActions.switchLdapToEmail(ldapPassword, email, password, token)(dispatch, getState).then(
(data) => {
if (data) {
if (data.follow_link) {
clientLogout(data.follow_link);
}
if (success) {
success(data);
}
} else if (data == null && error) {
const serverError = getState().requests.users.switchLogin.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function loadProfilesAndTeamMembers(page, perPage, teamId = TeamStore.getCurrentId(), success) {
UserActions.getProfilesInTeam(teamId, page, perPage)(dispatch, getState).then(
(data) => {
loadTeamMembersForProfilesList(data, teamId, success);
loadStatusesForProfilesList(data);
}
);
}
export function loadProfilesAndTeamMembersAndChannelMembers(page, perPage, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
UserActions.getProfilesInChannel(channelId, page, perPage)(dispatch, getState).then(
(data) => {
loadTeamMembersForProfilesList(
data,
teamId,
() => {
loadChannelMembersForProfilesList(data, channelId, success, error);
loadStatusesForProfilesList(data);
}
);
}
);
}
export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
const pid = profiles[i].id;
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
loadTeamMembersForProfiles(list, teamId, success, error);
}
export function loadProfilesWithoutTeam(page, perPage, success) {
UserActions.getProfilesWithoutTeam(page, perPage)(dispatch, getState).then(
(data) => {
loadStatusesForProfilesMap(data);
if (success) {
success(data);
}
}
);
}
function loadTeamMembersForProfiles(userIds, teamId, success, error) {
getTeamMembersByIds(teamId, userIds)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.teams.getTeamMembers.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function loadChannelMembersForProfilesMap(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
if (!profiles.hasOwnProperty(pid)) {
continue;
}
if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
getChannelMembersForUserIds(channelId, list, success, error);
}
export function loadTeamMembersAndChannelMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
loadTeamMembersForProfilesList(profiles, teamId, () => {
loadChannelMembersForProfilesList(profiles, channelId, success, error);
}, error);
}
export function loadChannelMembersForProfilesList(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
const pid = profiles[i].id;
if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
getChannelMembersForUserIds(channelId, list, success, error);
}
function populateDMChannelsWithProfiles(userIds) {
const currentUserId = UserStore.getCurrentId();
for (let i = 0; i < userIds.length; i++) {
const channelName = getDirectChannelName(currentUserId, userIds[i]);
const channel = ChannelStore.getByName(channelName);
const profilesInChannel = Selectors.getUserIdsInChannels(getState())[channel.id] || new Set();
if (channel && !profilesInChannel.has(userIds[i])) {
UserStore.saveUserIdInChannel(channel.id, userIds[i]);
}
}
}
function populateChannelWithProfiles(channelId, users) {
for (let i = 0; i < users.length; i++) {
UserStore.saveUserIdInChannel(channelId, users[i].id);
}
UserStore.emitInChannelChange();
}
export function loadNewDMIfNeeded(channelId) {
function checkPreference(channel) {
const userId = getUserIdFromChannelName(channel);
if (!userId) {
return;
}
const pref = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, false);
if (pref === false) {
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
const currentUserId = UserStore.getCurrentId();
savePreferencesRedux(currentUserId, [{user_id: currentUserId, category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: userId, value: 'true'}])(dispatch, getState);
loadProfilesForDM();
}
}
const channel = ChannelStore.get(channelId);
if (channel) {
checkPreference(channel);
} else {
getChannelAndMyMember(channelId)(dispatch, getState).then(
(data) => {
if (data) {
checkPreference(data.channel);
}
}
);
}
}
export function loadNewGMIfNeeded(channelId) {
function checkPreference() {
const pref = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, false);
if (pref === false) {
PreferenceStore.setPreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, 'true');
const currentUserId = UserStore.getCurrentId();
savePreferencesRedux(currentUserId, [{user_id: currentUserId, category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW, name: channelId, value: 'true'}])(dispatch, getState);
loadProfilesForGM();
}
}
const channel = ChannelStore.get(channelId);
if (channel) {
checkPreference();
} else {
getChannelAndMyMember(channelId)(dispatch, getState).then(
() => {
checkPreference();
}
);
}
}
export function loadProfilesForSidebar() {
loadProfilesForDM();
loadProfilesForGM();
}
export function loadProfilesForGM() {
const channels = ChannelStore.getChannels();
const newPreferences = [];
for (let i = 0; i < channels.length; i++) {
const channel = channels[i];
if (channel.type !== Constants.GM_CHANNEL) {
continue;
}
if (UserStore.getProfileListInChannel(channel.id).length >= Constants.MIN_USERS_IN_GM) {
continue;
}
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channel.id);
if (!isVisible) {
const member = ChannelStore.getMyMember(channel.id);
if (!member || (member.mention_count === 0 && member.msg_count >= channel.total_msg_count)) {
continue;
}
newPreferences.push({
user_id: UserStore.getCurrentId(),
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
name: channel.id,
value: 'true'
});
}
UserActions.getProfilesInChannel(channel.id, 0, Constants.MAX_USERS_IN_GM)(dispatch, getState).then(
(data) => {
populateChannelWithProfiles(channel.id, data);
}
);
}
if (newPreferences.length > 0) {
const currentUserId = UserStore.getCurrentId();
savePreferencesRedux(currentUserId, newPreferences)(dispatch, getState);
}
}
export function loadProfilesForDM() {
const channels = ChannelStore.getChannels();
const newPreferences = [];
const profilesToLoad = [];
const profileIds = [];
for (let i = 0; i < channels.length; i++) {
const channel = channels[i];
if (channel.type !== Constants.DM_CHANNEL) {
continue;
}
const teammateId = channel.name.replace(UserStore.getCurrentId(), '').replace('__', '');
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammateId);
if (!isVisible) {
const member = ChannelStore.getMyMember(channel.id);
if (!member || member.mention_count === 0) {
continue;
}
newPreferences.push({
user_id: UserStore.getCurrentId(),
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
name: teammateId,
value: 'true'
});
}
if (!UserStore.hasProfile(teammateId)) {
profilesToLoad.push(teammateId);
}
profileIds.push(teammateId);
}
if (newPreferences.length > 0) {
const currentUserId = UserStore.getCurrentId();
savePreferencesRedux(currentUserId, newPreferences)(dispatch, getState);
}
if (profilesToLoad.length > 0) {
UserActions.getProfilesByIds(profilesToLoad)(dispatch, getState).then(
() => {
populateDMChannelsWithProfiles(profileIds);
},
);
} else {
populateDMChannelsWithProfiles(profileIds);
}
}
export function saveTheme(teamId, theme, cb) {
const currentUserId = UserStore.getCurrentId();
const preference = [{
user_id: currentUserId,
category: Preferences.CATEGORY_THEME,
name: teamId,
value: JSON.stringify(theme)
}];
savePreferencesRedux(currentUserId, preference)(dispatch, getState).then(
() => {
onThemeSaved(teamId, theme, cb);
}
);
}
function onThemeSaved(teamId, theme, onSuccess) {
const themePreferences = PreferenceStore.getCategory(Preferences.CATEGORY_THEME);
if (teamId !== '' && themePreferences.size > 1) {
// no extra handling to be done to delete team-specific themes
onSuccess();
return;
}
const toDelete = [];
for (const [name] of themePreferences) {
if (name === '' || name === teamId) {
continue;
}
toDelete.push({
user_id: UserStore.getCurrentId(),
category: Preferences.CATEGORY_THEME,
name
});
}
if (toDelete.length > 0) {
// we're saving a new global theme so delete any team-specific ones
const currentUserId = UserStore.getCurrentId();
deletePreferences(currentUserId, toDelete)(dispatch, getState);
}
onSuccess();
}
export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
UserActions.searchProfiles(term, {team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
}
);
}
export function searchUsersNotInTeam(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
UserActions.searchProfiles(term, {not_in_team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
}
);
}
export function autocompleteUsersInChannel(username, channelId, success) {
const channel = ChannelStore.get(channelId);
const teamId = channel ? channel.team_id : TeamStore.getCurrentId();
UserActions.autocompleteUsers(username, teamId, channelId)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
}
);
}
export function autocompleteUsersInTeam(username, success) {
UserActions.autocompleteUsers(username, TeamStore.getCurrentId())(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
}
);
}
export function autocompleteUsers(username, success) {
UserActions.autocompleteUsers(username)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
}
);
}
export function updateUser(user, type, success, error) {
UserActions.updateMe(user)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateMe.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function generateMfaSecret(success, error) {
UserActions.generateMfaSecret(UserStore.getCurrentId())(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.generateMfaSecret.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateUserNotifyProps(props, success, error) {
UserActions.updateMe({notify_props: props})(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateMe.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateUserRoles(userId, newRoles, success, error) {
UserActions.updateUserRoles(userId, newRoles)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function activateMfa(code, success, error) {
UserActions.updateUserMfa(UserStore.getCurrentId(), true, code)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
},
);
}
export function deactivateMfa(success, error) {
UserActions.updateUserMfa(UserStore.getCurrentId(), false)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
},
);
}
export function checkMfa(loginId, success, error) {
if (global.window.mm_config.EnableMultifactorAuthentication !== 'true') {
success(false);
return;
}
UserActions.checkMfa(loginId)(dispatch, getState).then(
(data) => {
if (data != null && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.checkMfa.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateActive(userId, active, success, error) {
UserActions.updateUserActive(userId, active)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updatePassword(userId, currentPassword, newPassword, success, error) {
UserActions.updateUserPassword(userId, currentPassword, newPassword)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function verifyEmail(token, success, error) {
UserActions.verifyUserEmail(token)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.verifyEmail.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function resetPassword(token, password, success, error) {
UserActions.resetUserPassword(token, password)(dispatch, getState).then(
(data) => {
if (data) {
browserHistory.push('/login?extra=' + ActionTypes.PASSWORD_CHANGE);
if (success) {
success(data);
}
} else if (data == null && error) {
const serverError = getState().requests.users.passwordReset.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function resendVerification(email, success, error) {
UserActions.sendVerificationEmail(email)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.verifyEmail.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function loginById(userId, password, mfaToken, success, error) {
UserActions.loginById(userId, password, mfaToken)(dispatch, getState).then(
(ok) => {
if (ok && success) {
success();
} else if (!ok && error) {
const serverError = getState().requests.users.login.error;
if (serverError.server_error_id === 'api.context.mfa_required.app_error') {
if (success) {
success();
}
return;
}
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function createUserWithInvite(user, data, emailHash, inviteId, success, error) {
UserActions.createUser(user, data, emailHash, inviteId)(dispatch, getState).then(
(resp) => {
if (resp && success) {
success(resp);
} else if (resp == null && error) {
const serverError = getState().requests.users.create.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function webLogin(loginId, password, token, success, error) {
UserActions.login(loginId, password, token)(dispatch, getState).then(
(ok) => {
if (ok && success) {
success();
} else if (!ok && error) {
const serverError = getState().requests.users.login.error;
if (serverError.server_error_id === 'api.context.mfa_required.app_error') {
if (success) {
success();
}
return;
}
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function webLoginByLdap(loginId, password, token, success, error) {
UserActions.login(loginId, password, token, true)(dispatch, getState).then(
(ok) => {
if (ok && success) {
success();
} else if (!ok && error) {
const serverError = getState().requests.users.login.error;
if (serverError.server_error_id === 'api.context.mfa_required.app_error') {
if (success) {
success();
}
return;
}
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function getAuthorizedApps(success, error) {
Client4.getAuthorizedOAuthApps(getState().entities.users.currentUserId).then(
(authorizedApps) => {
if (success) {
success(authorizedApps);
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function deauthorizeOAuthApp(appId, success, error) {
Client4.deauthorizeOAuthApp(appId).then(
() => {
if (success) {
success();
}
}
).catch(
(err) => {
if (error) {
error(err);
}
}
);
}
export function uploadProfileImage(userPicture, success, error) {
UserActions.uploadProfileImage(Selectors.getCurrentUserId(getState()), userPicture)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function loadProfiles(page, perPage, success) {
UserActions.getProfiles(page, perPage)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
}
);
}
export function getMissingProfiles(ids) {
const missingIds = ids.filter((id) => !UserStore.hasProfile(id));
if (missingIds.length === 0) {
return;
}
UserActions.getProfilesByIds(missingIds)(dispatch, getState);
}
export function loadMyTeamMembers() {
getMyTeamMembers()(dispatch, getState).then(
() => {
getMyTeamUnreads()(dispatch, getState);
}
);
}
export function savePreferences(prefs, callback) {
const currentUserId = UserStore.getCurrentId();
savePreferencesRedux(currentUserId, prefs)(dispatch, getState).then(
() => callback()
);
}
export async function savePreference(category, name, value) {
const currentUserId = UserStore.getCurrentId();
return savePreferencesRedux(currentUserId, [{user_id: currentUserId, category, name, value}])(dispatch, getState);
}
export function autoResetStatus() {
return async (doDispatch, doGetState) => {
const {currentUserId} = getState().entities.users;
const userStatus = await UserActions.getStatus(currentUserId)(doDispatch, doGetState);
if (!userStatus.manual) {
return userStatus;
}
const autoReset = getBool(getState(), PreferencesRedux.CATEGORY_AUTO_RESET_MANUAL_STATUS, currentUserId, false);
if (autoReset) {
UserActions.setStatus({user_id: currentUserId, status: 'online'})(doDispatch, doGetState);
return userStatus;
}
return userStatus;
};
}
export function sendPasswordResetEmail(email, success, error) {
UserActions.sendPasswordResetEmail(email)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.passwordReset.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}

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

@@ -1,38 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import {WebrtcActionTypes} from 'utils/constants.jsx';
import {Client4} from 'mattermost-redux/client';
export function initWebrtc(userId, isCalling) {
AppDispatcher.handleServerAction({
type: WebrtcActionTypes.INITIALIZE,
user_id: userId,
is_calling: isCalling
});
}
export function handle(message) {
AppDispatcher.handleServerAction({
type: message.action,
message
});
}
export function webrtcToken(success, error) {
Client4.webrtcToken().then(
(data) => {
if (success) {
success(data);
}
}
).catch(
() => {
if (error) {
error();
}
}
);
}

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

@@ -1,473 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars
import WebSocketClient from 'client/web_websocket_client.jsx';
import * as WebrtcActions from './webrtc_actions.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
import * as StatusActions from 'actions/status_actions.jsx';
import {Constants, Preferences, SocketEvents, UserStatuses, ErrorBarTypes} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {batchActions} from 'redux-batched-actions';
import {Client4} from 'mattermost-redux/client';
import {getSiteURL} from 'utils/url.jsx';
import * as TeamActions from 'mattermost-redux/actions/teams';
import {viewChannel, getChannelAndMyMember, getChannelStats} from 'mattermost-redux/actions/channels';
import {getPosts, getProfilesAndStatusesForPosts} from 'mattermost-redux/actions/posts';
import {setServerVersion} from 'mattermost-redux/actions/general';
import {ChannelTypes, TeamTypes, UserTypes, PostTypes, EmojiTypes} from 'mattermost-redux/action_types';
const MAX_WEBSOCKET_FAILS = 7;
export function initialize() {
if (!window.WebSocket) {
console.log('Browser does not support websocket'); //eslint-disable-line no-console
return;
}
let connUrl = getSiteURL();
// replace the protocol with a websocket one
if (connUrl.startsWith('https:')) {
connUrl = connUrl.replace(/^https:/, 'wss:');
} else {
connUrl = connUrl.replace(/^http:/, 'ws:');
}
// append a port number if one isn't already specified
if (!(/:\d+$/).test(connUrl)) {
if (connUrl.startsWith('wss:')) {
connUrl += ':' + global.window.mm_config.WebsocketSecurePort;
} else {
connUrl += ':' + global.window.mm_config.WebsocketPort;
}
}
connUrl += Client4.getUrlVersion() + '/websocket';
WebSocketClient.setEventCallback(handleEvent);
WebSocketClient.setFirstConnectCallback(handleFirstConnect);
WebSocketClient.setReconnectCallback(() => reconnect(false));
WebSocketClient.setMissedEventCallback(() => reconnect(false));
WebSocketClient.setCloseCallback(handleClose);
WebSocketClient.initialize(connUrl);
}
export function close() {
WebSocketClient.close();
}
function reconnectWebSocket() {
close();
initialize();
}
export function reconnect(includeWebSocket = true) {
if (includeWebSocket) {
reconnectWebSocket();
}
loadChannelsForCurrentUser();
getPosts(ChannelStore.getCurrentId())(dispatch, getState);
StatusActions.loadStatusesForChannelAndSidebar();
ErrorStore.clearLastError();
ErrorStore.emitChange();
}
let intervalId = '';
const SYNC_INTERVAL_MILLISECONDS = 1000 * 60 * 15; // 15 minutes
export function startPeriodicSync() {
clearInterval(intervalId);
intervalId = setInterval(
() => {
if (UserStore.getCurrentUser() != null) {
reconnect(false);
}
},
SYNC_INTERVAL_MILLISECONDS
);
}
export function stopPeriodicSync() {
clearInterval(intervalId);
}
function handleFirstConnect() {
ErrorStore.clearLastError();
ErrorStore.emitChange();
}
function handleClose(failCount) {
if (failCount > MAX_WEBSOCKET_FAILS) {
ErrorStore.storeLastError({message: ErrorBarTypes.WEBSOCKET_PORT_ERROR});
}
ErrorStore.setConnectionErrorCount(failCount);
ErrorStore.emitChange();
}
function handleEvent(msg) {
switch (msg.event) {
case SocketEvents.POSTED:
case SocketEvents.EPHEMERAL_MESSAGE:
handleNewPostEvent(msg);
break;
case SocketEvents.POST_EDITED:
handlePostEditEvent(msg);
break;
case SocketEvents.POST_DELETED:
handlePostDeleteEvent(msg);
break;
case SocketEvents.LEAVE_TEAM:
handleLeaveTeamEvent(msg);
break;
case SocketEvents.UPDATE_TEAM:
handleUpdateTeamEvent(msg);
break;
case SocketEvents.ADDED_TO_TEAM:
handleTeamAddedEvent(msg);
break;
case SocketEvents.USER_ADDED:
handleUserAddedEvent(msg);
break;
case SocketEvents.USER_REMOVED:
handleUserRemovedEvent(msg);
break;
case SocketEvents.USER_UPDATED:
handleUserUpdatedEvent(msg);
break;
case SocketEvents.MEMBERROLE_UPDATED:
handleUpdateMemberRoleEvent(msg);
break;
case SocketEvents.CHANNEL_CREATED:
handleChannelCreatedEvent(msg);
break;
case SocketEvents.CHANNEL_DELETED:
handleChannelDeletedEvent(msg);
break;
case SocketEvents.CHANNEL_UPDATED:
handleChannelUpdatedEvent(msg);
break;
case SocketEvents.DIRECT_ADDED:
handleDirectAddedEvent(msg);
break;
case SocketEvents.PREFERENCE_CHANGED:
handlePreferenceChangedEvent(msg);
break;
case SocketEvents.PREFERENCES_CHANGED:
handlePreferencesChangedEvent(msg);
break;
case SocketEvents.PREFERENCES_DELETED:
handlePreferencesDeletedEvent(msg);
break;
case SocketEvents.TYPING:
handleUserTypingEvent(msg);
break;
case SocketEvents.STATUS_CHANGED:
handleStatusChangedEvent(msg);
break;
case SocketEvents.HELLO:
handleHelloEvent(msg);
break;
case SocketEvents.WEBRTC:
handleWebrtc(msg);
break;
case SocketEvents.REACTION_ADDED:
handleReactionAddedEvent(msg);
break;
case SocketEvents.REACTION_REMOVED:
handleReactionRemovedEvent(msg);
break;
case SocketEvents.EMOJI_ADDED:
handleAddEmoji(msg);
break;
case SocketEvents.CHANNEL_VIEWED:
handleChannelViewedEvent(msg);
break;
default:
}
}
function handleChannelUpdatedEvent(msg) {
const channel = JSON.parse(msg.data.channel);
dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel});
}
function handleNewPostEvent(msg) {
const post = JSON.parse(msg.data.post);
handleNewPost(post, msg);
getProfilesAndStatusesForPosts([post], dispatch, getState);
if (post.user_id !== UserStore.getCurrentId()) {
UserStore.setStatus(post.user_id, UserStatuses.ONLINE);
}
}
function handlePostEditEvent(msg) {
// Store post
const post = JSON.parse(msg.data.post);
dispatch({type: PostTypes.RECEIVED_POST, data: post});
// Update channel state
if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
if (window.isActive) {
viewChannel(ChannelStore.getCurrentId())(dispatch, getState);
}
}
// Needed for search store
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.POST_UPDATED,
post
});
}
function handlePostDeleteEvent(msg) {
const post = JSON.parse(msg.data.post);
dispatch({type: PostTypes.POST_DELETED, data: post});
// Needed for search store
AppDispatcher.handleViewAction({
type: Constants.ActionTypes.POST_DELETED,
post
});
}
async function handleTeamAddedEvent(msg) {
await TeamActions.getTeam(msg.data.team_id)(dispatch, getState);
await TeamActions.getMyTeamMembers()(dispatch, getState);
await TeamActions.getMyTeamUnreads()(dispatch, getState);
}
function handleLeaveTeamEvent(msg) {
if (UserStore.getCurrentId() === msg.data.user_id) {
TeamStore.removeMyTeamMember(msg.data.team_id);
// if they are on the team being removed redirect them to default team
if (TeamStore.getCurrentId() === msg.data.team_id) {
BrowserStore.removeGlobalItem('team');
BrowserStore.removeGlobalItem(msg.data.team_id);
if (!global.location.pathname.startsWith('/admin_console')) {
GlobalActions.redirectUserToDefaultTeam();
}
}
dispatch(batchActions([
{
type: UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM,
data: {user_id: msg.data.user_id},
id: msg.data.team_id
},
{
type: TeamTypes.REMOVE_MEMBER_FROM_TEAM,
data: {team_id: msg.data.team_id, user_id: msg.data.user_id}
}
]));
} else {
UserStore.removeProfileFromTeam(msg.data.team_id, msg.data.user_id);
TeamStore.removeMemberInTeam(msg.data.team_id, msg.data.user_id);
}
}
function handleUpdateTeamEvent(msg) {
TeamStore.updateTeam(msg.data.team);
}
function handleUpdateMemberRoleEvent(msg) {
const member = JSON.parse(msg.data.member);
TeamStore.updateMyRoles(member);
}
function handleDirectAddedEvent(msg) {
getChannelAndMyMember(msg.broadcast.channel_id)(dispatch, getState);
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, msg.data.teammate_id, 'true');
loadProfilesForSidebar();
}
function handleUserAddedEvent(msg) {
if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
getChannelStats(ChannelStore.getCurrentId())(dispatch, getState);
}
if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) {
getChannelAndMyMember(msg.broadcast.channel_id)(dispatch, getState);
}
}
function handleUserRemovedEvent(msg) {
if (UserStore.getCurrentId() === msg.broadcast.user_id) {
loadChannelsForCurrentUser();
if (msg.data.remover_id !== msg.broadcast.user_id &&
msg.data.channel_id === ChannelStore.getCurrentId() &&
$('#removed_from_channel').length > 0) {
var sentState = {};
sentState.channelName = ChannelStore.getCurrent().display_name;
sentState.remover = UserStore.getProfile(msg.data.remover_id).username;
BrowserStore.setItem('channel-removed-state', sentState);
$('#removed_from_channel').modal('show');
}
GlobalActions.toggleSideBarAction(false);
const townsquare = ChannelStore.getByName('town-square');
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townsquare.name);
dispatch({
type: ChannelTypes.LEAVE_CHANNEL,
data: {id: msg.data.channel_id, user_id: msg.broadcast.user_id}
});
} else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
getChannelStats(ChannelStore.getCurrentId())(dispatch, getState);
}
}
function handleUserUpdatedEvent(msg) {
const user = msg.data.user;
if (UserStore.getCurrentId() !== user.id) {
UserStore.saveProfile(user);
}
}
function handleChannelCreatedEvent(msg) {
const channelId = msg.data.channel_id;
const teamId = msg.data.team_id;
if (TeamStore.getCurrentId() === teamId && !ChannelStore.getChannelById(channelId)) {
getChannelAndMyMember(channelId)(dispatch, getState);
}
}
function handleChannelDeletedEvent(msg) {
if (ChannelStore.getCurrentId() === msg.data.channel_id) {
const teamUrl = TeamStore.getCurrentTeamRelativeUrl();
browserHistory.push(teamUrl + '/channels/' + Constants.DEFAULT_CHANNEL);
}
dispatch({type: ChannelTypes.RECEIVED_CHANNEL_DELETED, data: {id: msg.data.channel_id, team_id: msg.broadcast.team_id}}, getState);
loadChannelsForCurrentUser();
}
function handlePreferenceChangedEvent(msg) {
const preference = JSON.parse(msg.data.preference);
GlobalActions.emitPreferenceChangedEvent(preference);
}
function handlePreferencesChangedEvent(msg) {
const preferences = JSON.parse(msg.data.preferences);
GlobalActions.emitPreferencesChangedEvent(preferences);
}
function handlePreferencesDeletedEvent(msg) {
const preferences = JSON.parse(msg.data.preferences);
GlobalActions.emitPreferencesDeletedEvent(preferences);
}
function handleUserTypingEvent(msg) {
GlobalActions.emitRemoteUserTypingEvent(msg.broadcast.channel_id, msg.data.user_id, msg.data.parent_id);
if (msg.data.user_id !== UserStore.getCurrentId()) {
UserStore.setStatus(msg.data.user_id, UserStatuses.ONLINE);
}
}
function handleStatusChangedEvent(msg) {
UserStore.setStatus(msg.data.user_id, msg.data.status);
}
function handleHelloEvent(msg) {
setServerVersion(msg.data.server_version)(dispatch, getState);
}
function handleWebrtc(msg) {
const data = msg.data;
return WebrtcActions.handle(data);
}
function handleReactionAddedEvent(msg) {
const reaction = JSON.parse(msg.data.reaction);
dispatch({
type: PostTypes.RECEIVED_REACTION,
data: reaction
});
}
function handleAddEmoji(msg) {
const data = JSON.parse(msg.data.emoji);
dispatch({
type: EmojiTypes.RECEIVED_CUSTOM_EMOJI,
data
});
}
function handleReactionRemovedEvent(msg) {
const reaction = JSON.parse(msg.data.reaction);
dispatch({
type: PostTypes.REACTION_DELETED,
data: reaction
});
}
function handleChannelViewedEvent(msg) {
// Useful for when multiple devices have the app open to different channels
if (ChannelStore.getCurrentId() !== msg.data.channel_id &&
UserStore.getCurrentId() === msg.broadcast.user_id) {
// Mark previous and next channel as read
ChannelStore.resetCounts([msg.data.channel_id]);
}
}

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

@@ -1,7 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import WebSocketClient from './websocket_client.jsx';
var WebClient = new WebSocketClient();
export default WebClient;

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

@@ -1,196 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
const MAX_WEBSOCKET_FAILS = 7;
const MIN_WEBSOCKET_RETRY_TIME = 3000; // 3 sec
const MAX_WEBSOCKET_RETRY_TIME = 300000; // 5 mins
export default class WebSocketClient {
constructor() {
this.conn = null;
this.connectionUrl = null;
this.sequence = 1;
this.eventSequence = 0;
this.connectFailCount = 0;
this.eventCallback = null;
this.responseCallbacks = {};
this.firstConnectCallback = null;
this.reconnectCallback = null;
this.missedEventCallback = null;
this.errorCallback = null;
this.closeCallback = null;
}
initialize(connectionUrl = this.connectionUrl, token) {
if (this.conn) {
return;
}
if (connectionUrl == null) {
console.log('websocket must have connection url'); //eslint-disable-line no-console
return;
}
if (this.connectFailCount === 0) {
console.log('websocket connecting to ' + connectionUrl); //eslint-disable-line no-console
}
this.conn = new WebSocket(connectionUrl);
this.connectionUrl = connectionUrl;
this.conn.onopen = () => {
this.eventSequence = 0;
if (token) {
this.sendMessage('authentication_challenge', {token});
}
if (this.connectFailCount > 0) {
console.log('websocket re-established connection'); //eslint-disable-line no-console
if (this.reconnectCallback) {
this.reconnectCallback();
}
} else if (this.firstConnectCallback) {
this.firstConnectCallback();
}
this.connectFailCount = 0;
};
this.conn.onclose = () => {
this.conn = null;
this.sequence = 1;
if (this.connectFailCount === 0) {
console.log('websocket closed'); //eslint-disable-line no-console
}
this.connectFailCount++;
if (this.closeCallback) {
this.closeCallback(this.connectFailCount);
}
let retryTime = MIN_WEBSOCKET_RETRY_TIME;
// If we've failed a bunch of connections then start backing off
if (this.connectFailCount > MAX_WEBSOCKET_FAILS) {
retryTime = MIN_WEBSOCKET_RETRY_TIME * this.connectFailCount * this.connectFailCount;
if (retryTime > MAX_WEBSOCKET_RETRY_TIME) {
retryTime = MAX_WEBSOCKET_RETRY_TIME;
}
}
setTimeout(
() => {
this.initialize(connectionUrl, token);
},
retryTime
);
};
this.conn.onerror = (evt) => {
if (this.connectFailCount <= 1) {
console.log('websocket error'); //eslint-disable-line no-console
console.log(evt); //eslint-disable-line no-console
}
if (this.errorCallback) {
this.errorCallback(evt);
}
};
this.conn.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
if (msg.seq_reply) {
if (msg.error) {
console.log(msg); //eslint-disable-line no-console
}
if (this.responseCallbacks[msg.seq_reply]) {
this.responseCallbacks[msg.seq_reply](msg);
Reflect.deleteProperty(this.responseCallbacks, msg.seq_reply);
}
} else if (this.eventCallback) {
if (msg.seq !== this.eventSequence && this.missedEventCallback) {
console.log('missed websocket event, act_seq=' + msg.seq + ' exp_seq=' + this.eventSequence); //eslint-disable-line no-console
this.missedEventCallback();
}
this.eventSequence = msg.seq + 1;
this.eventCallback(msg);
}
};
}
setEventCallback(callback) {
this.eventCallback = callback;
}
setFirstConnectCallback(callback) {
this.firstConnectCallback = callback;
}
setReconnectCallback(callback) {
this.reconnectCallback = callback;
}
setMissedEventCallback(callback) {
this.missedEventCallback = callback;
}
setErrorCallback(callback) {
this.errorCallback = callback;
}
setCloseCallback(callback) {
this.closeCallback = callback;
}
close() {
this.connectFailCount = 0;
this.sequence = 1;
if (this.conn && this.conn.readyState === WebSocket.OPEN) {
this.conn.onclose = () => {}; //eslint-disable-line no-empty-function
this.conn.close();
this.conn = null;
console.log('websocket closed'); //eslint-disable-line no-console
}
}
sendMessage(action, data, responseCallback) {
const msg = {
action,
seq: this.sequence++,
data
};
if (responseCallback) {
this.responseCallbacks[msg.seq] = responseCallback;
}
if (this.conn && this.conn.readyState === WebSocket.OPEN) {
this.conn.send(JSON.stringify(msg));
} else if (!this.conn || this.conn.readyState === WebSocket.CLOSED) {
this.conn = null;
this.initialize();
}
}
userTyping(channelId, parentId, callback) {
const data = {};
data.channel_id = channelId;
data.parent_id = parentId;
this.sendMessage('user_typing', data, callback);
}
getStatuses(callback) {
this.sendMessage('get_statuses', null, callback);
}
getStatusesByIds(userIds, callback) {
const data = {};
data.user_ids = userIds;
this.sendMessage('get_statuses_by_ids', data, callback);
}
}

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

@@ -1,213 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {Modal} from 'react-bootstrap';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';
import Constants from 'utils/constants.jsx';
export default class AboutBuildModal extends React.Component {
constructor(props) {
super(props);
this.doHide = this.doHide.bind(this);
}
doHide() {
this.props.onModalDismissed();
}
render() {
const config = global.window.mm_config;
const license = global.window.mm_license;
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
let title = (
<FormattedMessage
id='about.teamEditiont0'
defaultMessage='Team Edition'
/>
);
let subTitle = (
<FormattedMessage
id='about.teamEditionSt'
defaultMessage='All your team communication in one place, instantly searchable and accessible anywhere.'
/>
);
let learnMore = (
<div>
<FormattedMessage
id='about.teamEditionLearn'
defaultMessage='Join the Mattermost community at '
/>
<a
target='_blank'
rel='noopener noreferrer'
href='http://www.mattermost.org/'
>
{'mattermost.org'}
</a>
</div>
);
let licensee;
if (config.BuildEnterpriseReady === 'true') {
title = (
<FormattedMessage
id='about.teamEditiont1'
defaultMessage='Enterprise Edition'
/>
);
subTitle = (
<FormattedMessage
id='about.enterpriseEditionSt'
defaultMessage='Modern communication from behind your firewall.'
/>
);
learnMore = (
<div>
<FormattedMessage
id='about.enterpriseEditionLearn'
defaultMessage='Learn more about Enterprise Edition at '
/>
<a
target='_blank'
rel='noopener noreferrer'
href='http://about.mattermost.com/'
>
{'about.mattermost.com'}
</a>
</div>
);
if (license.IsLicensed === 'true') {
title = (
<FormattedMessage
id='about.enterpriseEditione1'
defaultMessage='Enterprise Edition'
/>
);
licensee = (
<div className='form-group'>
<FormattedMessage
id='about.licensed'
defaultMessage='Licensed to:'
/>
&nbsp;{license.Company}
</div>
);
}
}
let version = '\u00a0' + config.Version;
if (config.BuildNumber !== config.Version) {
version += '\u00a0 (' + config.BuildNumber + ')';
}
return (
<Modal
dialogClassName='about-modal'
show={this.props.show}
onHide={this.doHide}
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='about.title'
defaultMessage='About Mattermost'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className='about-modal__content'>
<div className='about-modal__logo'>
<span
className='icon'
dangerouslySetInnerHTML={{__html: mattermostLogo}}
/>
</div>
<div>
<h3 className='about-modal__title'>{'Mattermost'} {title}</h3>
<p className='about-modal__subtitle padding-bottom'>{subTitle}</p>
<div className='form-group less'>
<div>
<FormattedMessage
id='about.version'
defaultMessage='Version:'
/>
<span id='versionString'>{version}</span>
</div>
<div>
<FormattedMessage
id='about.database'
defaultMessage='Database:'
/>
{'\u00a0' + config.SQLDriverName}
</div>
</div>
{licensee}
</div>
</div>
<div className='about-modal__footer'>
{learnMore}
<div className='form-group about-modal__copyright'>
<FormattedMessage
id='about.copyright'
defaultMessage='Copyright 2015 - {currentYear} Mattermost, Inc. All rights reserved'
values={{
currentYear: new Date().getFullYear()
}}
/>
</div>
</div>
<div className='about-modal__notice form-group padding-top x2'>
<p>
<FormattedHTMLMessage
id='about.notice'
defaultMessage='Mattermost is made possible by the open source software used in our <a href="https://about.mattermost.com/platform-notice-txt/" target="_blank">platform</a>, <a href="https://about.mattermost.com/desktop-notice-txt/" target="_blank">desktop</a> and <a href="https://about.mattermost.com/mobile-notice-txt/" target="_blank">mobile</a> apps.'
/>
</p>
</div>
<div className='about-modal__hash'>
<p>
<FormattedMessage
id='about.hash'
defaultMessage='Build Hash:'
/>
&nbsp;{config.BuildHash}
<br/>
<FormattedMessage
id='about.hashee'
defaultMessage='EE Build Hash:'
/>
&nbsp;{config.BuildHashEnterprise}
</p>
<p>
<FormattedMessage
id='about.date'
defaultMessage='Build Date:'
/>
&nbsp;{config.BuildDate}
</p>
</div>
</Modal.Body>
</Modal>
);
}
}
AboutBuildModal.defaultProps = {
show: false
};
AboutBuildModal.propTypes = {
show: PropTypes.bool.isRequired,
onModalDismissed: PropTypes.func.isRequired
};

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

@@ -1,109 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import AuditTable from 'components/audit_table.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
export default class AccessHistoryModal extends React.Component {
static propTypes = {
onHide: PropTypes.func.isRequired,
actions: PropTypes.shape({
getUserAudits: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.onAuditChange = this.onAuditChange.bind(this);
this.onShow = this.onShow.bind(this);
this.onHide = this.onHide.bind(this);
const state = this.getStateFromStoresForAudits();
state.moreInfo = [];
state.show = true;
this.state = state;
}
getStateFromStoresForAudits() {
return {
audits: UserStore.getAudits()
};
}
onShow() {
this.props.actions.getUserAudits(UserStore.getCurrentId(), 0, 200);
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
}
onHide() {
this.setState({show: false});
}
componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange);
this.onShow();
}
componentWillUnmount() {
UserStore.removeAuditsChangeListener(this.onAuditChange);
}
onAuditChange() {
const newState = this.getStateFromStoresForAudits();
if (!Utils.areObjectsEqual(newState.audits, this.state.audits)) {
this.setState(newState);
}
}
render() {
let content;
if (this.state.audits.length === 0) {
content = (<LoadingScreen/>);
} else {
content = (
<AuditTable
audits={this.state.audits}
showIp={true}
showSession={true}
/>
);
}
return (
<Modal
dialogClassName='modal--scroll'
show={this.state.show}
onHide={this.onHide}
onExited={this.props.onHide}
bsSize='large'
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='access_history.title'
defaultMessage='Access History'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
{content}
</Modal.Body>
</Modal>
);
}
}

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

@@ -1,24 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getUserAudits} from 'mattermost-redux/actions/users';
import AccessHistoryModal from './access_history_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUserAudits
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AccessHistoryModal);

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

@@ -1,338 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
import {General} from 'mattermost-redux/constants';
export default class ActivityLogModal extends React.Component {
static propTypes = {
onHide: PropTypes.func.isRequired,
actions: PropTypes.shape({
getSessions: PropTypes.func.isRequired,
revokeSession: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.submitRevoke = this.submitRevoke.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleMoreInfo = this.handleMoreInfo.bind(this);
this.onHide = this.onHide.bind(this);
this.onShow = this.onShow.bind(this);
const state = this.getStateFromStores();
state.moreInfo = [];
state.show = true;
this.state = state;
}
getStateFromStores() {
return {
sessions: UserStore.getSessions(),
clientError: null
};
}
submitRevoke(altId, e) {
e.preventDefault();
var modalContent = $(e.target).closest('.modal-content');
modalContent.addClass('animation--highlight');
setTimeout(() => {
modalContent.removeClass('animation--highlight');
}, 1500);
this.props.actions.revokeSession(UserStore.getCurrentId(), altId).then(() => {
this.props.actions.getSessions(UserStore.getCurrentId());
});
}
onShow() {
this.props.actions.getSessions(UserStore.getCurrentId());
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
}
onHide() {
this.setState({show: false});
}
componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
this.onShow();
}
componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
}
onListenerChange() {
const newState = this.getStateFromStores();
if (!Utils.areObjectsEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
}
}
handleMoreInfo(index) {
const newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo});
}
isMobileSession = (session) => {
return session.device_id && (session.device_id.includes('apple') || session.device_id.includes('android'));
};
mobileSessionInfo = (session) => {
let deviceTypeId;
let deviceTypeMessage;
let devicePicture;
if (session.device_id.includes('apple')) {
devicePicture = 'fa fa-apple';
deviceTypeId = 'activity_log_modal.iphoneNativeClassicApp';
deviceTypeMessage = 'iPhone Native Classic App';
if (session.device_id.includes(General.PUSH_NOTIFY_APPLE_REACT_NATIVE)) {
deviceTypeId = 'activity_log_modal.iphoneNativeApp';
deviceTypeMessage = 'iPhone Native App';
}
} else if (session.device_id.includes('android')) {
devicePicture = 'fa fa-android';
deviceTypeId = 'activity_log_modal.androidNativeClassicApp';
deviceTypeMessage = 'Android Native Classic App';
if (session.device_id.includes(General.PUSH_NOTIFY_ANDROID_REACT_NATIVE)) {
deviceTypeId = 'activity_log_modal.androidNativeApp';
deviceTypeMessage = 'Android Native App';
}
}
return {
devicePicture,
devicePlatform: (
<FormattedMessage
id={deviceTypeId}
defaultMessage={deviceTypeMessage}
/>
)
};
};
render() {
const activityList = [];
for (let i = 0; i < this.state.sessions.length; i++) {
const currentSession = this.state.sessions[i];
const lastAccessTime = new Date(currentSession.last_activity_at);
const firstAccessTime = new Date(currentSession.create_at);
let devicePlatform = currentSession.props.platform;
let devicePicture = '';
if (currentSession.props.type === 'UserAccessToken') {
continue;
}
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
} else if (this.isMobileSession(currentSession)) {
const sessionInfo = this.mobileSessionInfo(currentSession);
devicePicture = sessionInfo.devicePicture;
devicePlatform = sessionInfo.devicePlatform;
} else if (currentSession.props.platform === 'Macintosh' ||
currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
} else if (currentSession.props.platform === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
devicePlatform = (
<FormattedMessage
id='activity_log_modal.android'
defaultMessage='Android'
/>
);
devicePicture = 'fa fa-android';
} else {
devicePicture = 'fa fa-linux';
}
} else if (currentSession.props.os.indexOf('Linux') !== -1) {
devicePicture = 'fa fa-linux';
}
if (currentSession.props.browser.indexOf('Desktop App') !== -1) {
devicePlatform = (
<FormattedMessage
id='activity_log_modal.desktop'
defaultMessage='Native Desktop App'
/>
);
}
let moreInfo;
if (this.state.moreInfo[i]) {
moreInfo = (
<div>
<div>
<FormattedMessage
id='activity_log.firstTime'
defaultMessage='First time active: {date}, {time}'
values={{
date: (
<FormattedDate
value={firstAccessTime}
day='2-digit'
month='long'
year='numeric'
/>
),
time: (
<FormattedTime
value={firstAccessTime}
hour='2-digit'
minute='2-digit'
/>
)
}}
/>
</div>
<div>
<FormattedMessage
id='activity_log.os'
defaultMessage='OS: {os}'
values={{
os: currentSession.props.os
}}
/>
</div>
<div>
<FormattedMessage
id='activity_log.browser'
defaultMessage='Browser: {browser}'
values={{
browser: currentSession.props.browser
}}
/>
</div>
<div>
<FormattedMessage
id='activity_log.sessionId'
defaultMessage='Session ID: {id}'
values={{
id: currentSession.id
}}
/>
</div>
</div>
);
} else {
moreInfo = (
<a
className='theme'
href='#'
onClick={this.handleMoreInfo.bind(this, i)}
>
<FormattedMessage
id='activity_log.moreInfo'
defaultMessage='More info'
/>
</a>
);
}
activityList[i] = (
<div
key={'activityLogEntryKey' + i}
className='activity-log__table'
>
<div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture}/>{devicePlatform}</div>
<div className='report__info'>
<div>
<FormattedMessage
id='activity_log.lastActivity'
defaultMessage='Last activity: {date}, {time}'
values={{
date: (
<FormattedDate
value={lastAccessTime}
day='2-digit'
month='long'
year='numeric'
/>
),
time: (
<FormattedTime
value={lastAccessTime}
hour='2-digit'
minute='2-digit'
/>
)
}}
/>
</div>
{moreInfo}
</div>
</div>
<div className='activity-log__action'>
<button
onClick={this.submitRevoke.bind(this, currentSession.id)}
className='btn btn-primary'
>
<FormattedMessage
id='activity_log.logout'
defaultMessage='Logout'
/>
</button>
</div>
</div>
);
}
let content;
if (this.state.sessions.loading) {
content = <LoadingScreen/>;
} else {
content = <form role='form'>{activityList}</form>;
}
return (
<Modal
dialogClassName='modal--scroll'
show={this.state.show}
onHide={this.onHide}
onExited={this.props.onHide}
bsSize='large'
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='activity_log.activeSessions'
defaultMessage='Active Sessions'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<p className='session-help-text'>
<FormattedMessage
id='activity_log.sessionsDescription'
defaultMessage="Sessions are created when you log in to a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. If you want to log out sooner, use the 'Logout' button below to end a session."
/>
</p>
{content}
</Modal.Body>
</Modal>
);
}
}

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

@@ -1,25 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {revokeSession, getSessions} from 'mattermost-redux/actions/users';
import ActivityLogModal from './activity_log_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getSessions,
revokeSession
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ActivityLogModal);

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

@@ -1,264 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import MultiSelect from 'components/multiselect/multiselect.jsx';
import ProfilePicture from 'components/profile_picture.jsx';
import {addUsersToTeam} from 'actions/team_actions.jsx';
import {searchUsersNotInTeam} from 'actions/user_actions.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import {displayEntireNameForUser} from 'utils/utils.jsx';
import {Client4} from 'mattermost-redux/client';
import PropTypes from 'prop-types';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
import {searchProfilesNotInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
const MAX_SELECTABLE_VALUES = 20;
export default class AddUsersToTeam extends React.Component {
static propTypes = {
onModalDismissed: PropTypes.func,
actions: PropTypes.shape({
getProfilesNotInTeam: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.handleHide = this.handleHide.bind(this);
this.handleExit = this.handleExit.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.onChange = this.onChange.bind(this);
this.search = this.search.bind(this);
this.addValue = this.addValue.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.searchTimeoutId = 0;
this.state = {
users: Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true)),
values: [],
show: true,
search: false
};
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
UserStore.addNotInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
UserStore.removeNotInTeamChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
}
handleHide() {
this.setState({show: false});
}
handleExit() {
if (this.exitToChannel) {
browserHistory.push(this.exitToChannel);
}
if (this.props.onModalDismissed) {
this.props.onModalDismissed();
}
}
handleSubmit(e) {
if (e) {
e.preventDefault();
}
const userIds = this.state.values.map((v) => v.id);
if (userIds.length === 0) {
return;
}
addUsersToTeam(TeamStore.getCurrentId(), userIds);
this.handleHide();
}
addValue(value) {
const values = Object.assign([], this.state.values);
if (values.indexOf(value) === -1) {
values.push(value);
}
this.setState({values});
}
onChange() {
let users;
if (this.term) {
users = Object.assign([], searchProfilesNotInCurrentTeam(store.getState(), this.term, true));
} else {
users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
}
for (let i = 0; i < users.length; i++) {
const user = Object.assign({}, users[i]);
user.value = user.id;
user.label = '@' + user.username;
users[i] = user;
}
this.setState({
users
});
}
handlePageChange(page, prevPage) {
if (page > prevPage) {
this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), page + 1, USERS_PER_PAGE);
}
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.onChange();
return;
}
this.searchTimeoutId = setTimeout(
() => {
searchUsersNotInTeam(term, TeamStore.getCurrentId(), {});
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
}
handleDelete(values) {
this.setState({values});
}
renderOption(option, isSelected, onAdd) {
var rowSelected = '';
if (isSelected) {
rowSelected = 'more-modal__row--selected';
}
return (
<div
key={option.id}
ref={isSelected ? 'selected' : option.id}
className={'more-modal__row clickable ' + rowSelected}
onClick={() => onAdd(option)}
>
<ProfilePicture
src={Client4.getProfilePictureUrl(option.id, option.last_picture_update)}
width='32'
height='32'
/>
<div
className='more-modal__details'
>
<div className='more-modal__name'>
{displayEntireNameForUser(option)}
</div>
<div className='more-modal__description'>
{option.email}
</div>
</div>
<div className='more-modal__actions'>
<div className='more-modal__actions--round'>
<i className='fa fa-plus'/>
</div>
</div>
</div>
);
}
renderValue(user) {
return user.username;
}
render() {
const numRemainingText = (
<FormattedMessage
id='multiselect.numPeopleRemaining'
defaultMessage='Use ↑↓ to browse, ↵ to select. You can add {num, number} more {num, plural, one {person} other {people}}. '
values={{
num: MAX_SELECTABLE_VALUES - this.state.values.length
}}
/>
);
const buttonSubmitText = (
<FormattedMessage
id='multiselect.add'
defaultMessage='Add'
/>
);
let users = [];
if (this.state.users) {
users = this.state.users.filter((user) => user.delete_at === 0);
}
return (
<Modal
dialogClassName={'more-modal more-direct-channels'}
show={this.state.show}
onHide={this.handleHide}
onExited={this.handleExit}
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='add_users_to_team.title'
defaultMessage='Add New Members To {teamName} Team'
values={{
teamName: (
<strong>{TeamStore.getCurrent().display_name}</strong>
)
}}
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<MultiSelect
key='addUsersToTeamKey'
options={users}
optionRenderer={this.renderOption}
values={this.state.values}
valueRenderer={this.renderValue}
perPage={USERS_PER_PAGE}
handlePageChange={this.handlePageChange}
handleInput={this.search}
handleDelete={this.handleDelete}
handleAdd={this.addValue}
handleSubmit={this.handleSubmit}
maxValues={MAX_SELECTABLE_VALUES}
numRemainingText={numRemainingText}
buttonSubmitText={buttonSubmitText}
/>
</Modal.Body>
</Modal>
);
}
}

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

@@ -1,24 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfilesNotInTeam} from 'mattermost-redux/actions/users';
import AddUsersToTeam from './add_users_to_team.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfilesNotInTeam
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AddUsersToTeam);

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

@@ -1,68 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import 'bootstrap';
import AnnouncementBar from 'components/announcement_bar';
import AdminSidebar from './admin_sidebar.jsx';
import {reloadIfServerVersionChanged} from 'actions/global_actions.jsx';
export default class AdminConsole extends React.Component {
static propTypes = {
/*
* Children components to render
*/
children: PropTypes.node.isRequired,
/*
* Object representing the config file
*/
config: PropTypes.object.isRequired,
actions: PropTypes.shape({
/*
* Function to get the config file
*/
getConfig: PropTypes.func.isRequired
}).isRequired
}
componentWillMount() {
this.props.actions.getConfig();
reloadIfServerVersionChanged();
}
render() {
const config = this.props.config;
if (Object.keys(config).length === 0) {
return <div/>;
}
if (config && Object.keys(config).length === 0 && config.constructor === 'Object') {
return (
<div className='admin-console__wrapper'>
<AnnouncementBar/>
<div className='admin-console'/>
</div>
);
}
// not every page in the system console will need the config, but the vast majority will
const children = React.cloneElement(this.props.children, {
config
});
return (
<div className='admin-console__wrapper'>
<AnnouncementBar/>
<div className='admin-console'>
<AdminSidebar/>
{children}
</div>
</div>
);
}
}

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

@@ -1,223 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import AboutBuildModal from 'components/about_build_modal.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router/es6';
import React from 'react';
import * as Utils from 'utils/utils.jsx';
export default class AdminNavbarDropdown extends React.Component {
constructor(props) {
super(props);
this.blockToggle = false;
this.onTeamChange = this.onTeamChange.bind(this);
this.handleAboutModal = this.handleAboutModal.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
this.state = {
teams: TeamStore.getAll(),
teamMembers: TeamStore.getMyTeamMembers(),
showAboutModal: false
};
}
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
this.blockToggle = true;
setTimeout(() => {
this.blockToggle = false;
}, 100);
});
TeamStore.addChangeListener(this.onTeamChange);
}
componentWillUnmount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
TeamStore.removeChangeListener(this.onTeamChange);
}
handleAboutModal(e) {
e.preventDefault();
this.setState({showAboutModal: true});
}
aboutModalDismissed() {
this.setState({showAboutModal: false});
}
onTeamChange() {
this.setState({
teams: TeamStore.getAll(),
teamMembers: TeamStore.getMyTeamMembers()
});
}
render() {
var teamsArray = []; // Array of team objects
var teams = []; // Array of team components
let switchTeams;
if (this.state.teamMembers && this.state.teamMembers.length > 0) {
for (const index in this.state.teamMembers) {
if (this.state.teamMembers.hasOwnProperty(index)) {
const teamMember = this.state.teamMembers[index];
const team = this.state.teams[teamMember.team_id];
teamsArray.push(team);
}
}
// Sort teams alphabetically with display_name
teamsArray = teamsArray.sort(sortTeamsByDisplayName);
for (const team of teamsArray) {
teams.push(
<li key={'team_' + team.name}>
<Link
id={'swithTo' + Utils.createSafeId(team.name)}
to={'/' + team.name + '/channels/town-square'}
>
<FormattedMessage
id='navbar_dropdown.switchTo'
defaultMessage='Switch to '
/>
{team.display_name}
</Link>
</li>
);
}
teams.push(
<li
key='teamDiv'
className='divider'
/>
);
} else {
switchTeams = (
<li>
<Link
to={'/select_team'}
>
<i className='fa fa-exchange'/>
<FormattedMessage
id='admin.nav.switch'
defaultMessage='Team Selection'
/>
</Link>
</li>
);
}
return (
<ul className='nav navbar-nav navbar-right admin-navbar-dropdown'>
<li
ref='dropdown'
className='dropdown'
>
<a
href='#'
id='adminNavbarDropdownButton'
className='dropdown-toggle admin-navbar-dropdown__toggle'
data-toggle='dropdown'
role='button'
aria-expanded='false'
>
<span
className='dropdown__icon admin-navbar-dropdown__icon'
dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}
/>
</a>
<ul
className='dropdown-menu'
role='menu'
>
{teams}
{switchTeams}
<li
key='teamDiv'
className='divider'
/>
<li>
<Link
to='https://about.mattermost.com/administrators-guide/'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='admin.nav.administratorsGuide'
defaultMessage='Administrator Guide'
/>
</Link>
</li>
<li>
<Link
to='https://about.mattermost.com/troubleshooting-forum/'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='admin.nav.troubleshootingForum'
defaultMessage='Troubleshooting Forum'
/>
</Link>
</li>
<li>
<Link
to='https://about.mattermost.com/commercial-support/'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='admin.nav.commercialSupport'
defaultMessage='Commercial Support'
/>
</Link>
</li>
<li>
<a
href='#'
onClick={this.handleAboutModal}
>
<FormattedMessage
id='navbar_dropdown.about'
defaultMessage='About Mattermost'
/>
</a>
</li>
<li className='divider'/>
<li>
<a
href='#'
id='logout'
onClick={() => GlobalActions.emitUserLoggedOutEvent()}
>
<FormattedMessage
id='admin.nav.logout'
defaultMessage='Logout'
/>
</a>
</li>
<AboutBuildModal
show={this.state.showAboutModal}
onModalDismissed={this.aboutModalDismissed}
/>
</ul>
</li>
</ul>
);
}
}

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

@@ -1,147 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import FormError from 'components/form_error.jsx';
import SaveButton from 'components/admin_console/save_button.jsx';
import {saveConfig} from 'actions/admin_actions.jsx';
export default class AdminSettings extends React.Component {
static propTypes = {
/*
* Object representing the config file
*/
config: PropTypes.object
}
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.doSubmit = this.doSubmit.bind(this);
this.state = Object.assign(this.getStateFromConfig(props.config), {
saveNeeded: false,
saving: false,
serverError: null
});
}
handleChange(id, value) {
this.setState({
saveNeeded: true,
[id]: value
});
}
handleSubmit(e) {
e.preventDefault();
this.doSubmit();
}
doSubmit(callback) {
this.setState({
saving: true,
serverError: null
});
// clone config so that we aren't modifying data in the stores
let config = JSON.parse(JSON.stringify(this.props.config));
config = this.getConfigFromState(config);
saveConfig(
config,
(savedConfig) => {
this.setState(this.getStateFromConfig(savedConfig));
this.setState({
saveNeeded: false,
saving: false
});
if (callback) {
callback();
}
if (this.handleSaved) {
this.handleSaved(config);
}
},
(err) => {
this.setState({
saving: false,
serverError: err.message
});
if (callback) {
callback();
}
if (this.handleSaved) {
this.handleSaved(config);
}
}
);
}
parseInt(str, defaultValue) {
const n = parseInt(str, 10);
if (isNaN(n)) {
if (defaultValue) {
return defaultValue;
}
return 0;
}
return n;
}
parseIntNonZero(str, defaultValue) {
const n = parseInt(str, 10);
if (isNaN(n) || n < 1) {
if (defaultValue) {
return defaultValue;
}
return 1;
}
return n;
}
render() {
return (
<div className='wrapper--fixed'>
<h3 className='admin-console-header'>
{this.renderTitle()}
</h3>
<form
className='form-horizontal'
role='form'
onSubmit={this.handleSubmit}
>
{this.renderSettings()}
<div className='form-group'>
<FormError error={this.state.serverError}/>
</div>
<div className='form-group'>
<div className='col-sm-12'>
<SaveButton
saving={this.state.saving}
disabled={!this.state.saveNeeded || (this.canSave && !this.canSave())}
onClick={this.handleSubmit}
/>
</div>
</div>
</form>
</div>
);
}
}

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

@@ -1,702 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import * as Utils from 'utils/utils.jsx';
import AdminSidebarCategory from './admin_sidebar_category.jsx';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarSection from './admin_sidebar_section.jsx';
export default class AdminSidebar extends React.Component {
static get contextTypes() {
return {
router: PropTypes.object.isRequired
};
}
constructor(props) {
super(props);
this.updateTitle = this.updateTitle.bind(this);
}
componentDidMount() {
this.updateTitle();
if (!Utils.isMobile()) {
$('.admin-sidebar .nav-pills__container').perfectScrollbar({
suppressScrollX: true
});
}
}
componentDidUpdate() {
if (!Utils.isMobile()) {
$('.admin-sidebar .nav-pills__container').perfectScrollbar({
suppressScrollX: true
});
}
}
updateTitle() {
let currentSiteName = '';
if (global.window.mm_config.SiteName != null) {
currentSiteName = global.window.mm_config.SiteName;
}
document.title = Utils.localizeMessage('sidebar_right_menu.console', 'System Console') + ' - ' + currentSiteName;
}
render() {
let oauthSettings = null;
let ldapSettings = null;
let samlSettings = null;
let clusterSettings = null;
let metricsSettings = null;
let complianceSettings = null;
let mfaSettings = null;
let pluginSettings = null;
let license = null;
let audits = null;
let policy = null;
if (window.mm_config.BuildEnterpriseReady === 'true') {
license = (
<AdminSidebarSection
name='license'
title={
<FormattedMessage
id='admin.sidebar.license'
defaultMessage='Edition and License'
/>
}
/>
);
}
if (window.mm_license.IsLicensed === 'true') {
if (global.window.mm_license.LDAP === 'true') {
ldapSettings = (
<AdminSidebarSection
name='ldap'
title={
<FormattedMessage
id='admin.sidebar.ldap'
defaultMessage='AD/LDAP'
/>
}
/>
);
}
if (global.window.mm_license.Cluster === 'true') {
clusterSettings = (
<AdminSidebarSection
name='cluster'
title={
<FormattedMessage
id='admin.sidebar.cluster'
defaultMessage='High Availability'
/>
}
/>
);
}
if (global.window.mm_license.Metrics === 'true') {
metricsSettings = (
<AdminSidebarSection
name='metrics'
title={
<FormattedMessage
id='admin.sidebar.metrics'
defaultMessage='Performance Monitoring'
/>
}
/>
);
}
if (global.window.mm_license.SAML === 'true') {
samlSettings = (
<AdminSidebarSection
name='saml'
title={
<FormattedMessage
id='admin.sidebar.saml'
defaultMessage='SAML 2.0'
/>
}
/>
);
}
if (global.window.mm_license.Compliance === 'true') {
complianceSettings = (
<AdminSidebarSection
name='compliance'
title={
<FormattedMessage
id='admin.sidebar.compliance'
defaultMessage='Compliance'
/>
}
/>
);
}
if (global.window.mm_license.MFA === 'true') {
mfaSettings = (
<AdminSidebarSection
name='mfa'
title={
<FormattedMessage
id='admin.sidebar.mfa'
defaultMessage='MFA'
/>
}
/>
);
}
oauthSettings = (
<AdminSidebarSection
name='oauth'
title={
<FormattedMessage
id='admin.sidebar.oauth'
defaultMessage='OAuth 2.0'
/>
}
/>
);
policy = (
<AdminSidebarSection
name='policy'
title={
<FormattedMessage
id='admin.sidebar.policy'
defaultMessage='Policy'
/>
}
/>
);
} else {
oauthSettings = (
<AdminSidebarSection
name='gitlab'
title={
<FormattedMessage
id='admin.sidebar.gitlab'
defaultMessage='GitLab'
/>
}
/>
);
}
if (window.mm_license.IsLicensed === 'true') {
audits = (
<AdminSidebarSection
name='audits'
title={
<FormattedMessage
id='admin.sidebar.audits'
defaultMessage='Complaince and Auditing'
/>
}
/>
);
}
let customBranding = null;
if (window.mm_license.IsLicensed === 'true') {
customBranding = (
<AdminSidebarSection
name='custom_brand'
title={
<FormattedMessage
id='admin.sidebar.customBrand'
defaultMessage='Custom Branding'
/>
}
/>
);
}
let otherCategory = null;
if (license || audits) {
otherCategory = (
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-wrench'
title={
<FormattedMessage
id='admin.sidebar.other'
defaultMessage='OTHER'
/>
}
>
{license}
{audits}
</AdminSidebarCategory>
);
}
const webrtcSettings = (
<AdminSidebarSection
name='webrtc'
title={
<FormattedMessage
id='admin.sidebar.webrtc'
defaultMessage='WebRTC (Beta)'
/>
}
/>
);
let elasticSearchSettings = null;
if (window.mm_license.IsLicensed === 'true') {
elasticSearchSettings = (
<AdminSidebarSection
name='elasticsearch'
title={
<FormattedMessage
id='admin.sidebar.elasticsearch'
defaultMessage='Elasticsearch (Beta)'
/>
}
/>
);
}
if (window.mm_config.PluginsEnabled === 'true' && window.mm_license.IsLicensed === 'true') {
pluginSettings = (
<AdminSidebarSection
name='plugins'
title={
<FormattedMessage
id='admin.sidebar.plugins'
defaultMessage='Plugins (experimental)'
/>
}
/>
);
}
const SHOW_CLIENT_VERSIONS = false;
let clientVersions = null;
if (SHOW_CLIENT_VERSIONS) {
clientVersions = (
<AdminSidebarSection
name='client_versions'
title={
<FormattedMessage
id='admin.sidebar.client_versions'
defaultMessage='Client Versions'
/>
}
/>
);
}
return (
<div className='admin-sidebar'>
<AdminSidebarHeader/>
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-bar-chart'
title={
<FormattedMessage
id='admin.sidebar.reports'
defaultMessage='REPORTING'
/>
}
>
<AdminSidebarSection
name='system_analytics'
title={
<FormattedMessage
id='admin.sidebar.view_statistics'
defaultMessage='Site Statistics'
/>
}
/>
<AdminSidebarSection
name='team_analytics'
title={
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='Team Statistics'
/>
}
/>
<AdminSidebarSection
name='users'
title={
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='Users'
/>
}
/>
<AdminSidebarSection
name='logs'
title={
<FormattedMessage
id='admin.sidebar.logs'
defaultMessage='Logs'
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
sectionClass='sections--settings'
parentLink='/admin_console'
icon='fa-gear'
title={
<FormattedMessage
id='admin.sidebar.settings'
defaultMessage='SETTINGS'
/>
}
>
<AdminSidebarSection
name='general'
type='text'
title={
<FormattedMessage
id='admin.sidebar.general'
defaultMessage='General'
/>
}
>
<AdminSidebarSection
name='configuration'
title={
<FormattedMessage
id='admin.sidebar.configuration'
defaultMessage='Configuration'
/>
}
/>
<AdminSidebarSection
name='localization'
title={
<FormattedMessage
id='admin.sidebar.localization'
defaultMessage='Localization'
/>
}
/>
<AdminSidebarSection
name='users_and_teams'
title={
<FormattedMessage
id='admin.sidebar.usersAndTeams'
defaultMessage='Users and Teams'
/>
}
/>
{policy}
<AdminSidebarSection
name='privacy'
title={
<FormattedMessage
id='admin.sidebar.privacy'
defaultMessage='Privacy'
/>
}
/>
{complianceSettings}
<AdminSidebarSection
name='logging'
title={
<FormattedMessage
id='admin.sidebar.logging'
defaultMessage='Logging'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='authentication'
type='text'
title={
<FormattedMessage
id='admin.sidebar.authentication'
defaultMessage='Authentication'
/>
}
>
<AdminSidebarSection
name='authentication_email'
title={
<FormattedMessage
id='admin.sidebar.email'
defaultMessage='Email'
/>
}
/>
{oauthSettings}
{ldapSettings}
{samlSettings}
{mfaSettings}
</AdminSidebarSection>
<AdminSidebarSection
name='security'
type='text'
title={
<FormattedMessage
id='admin.sidebar.security'
defaultMessage='Security'
/>
}
>
<AdminSidebarSection
name='sign_up'
title={
<FormattedMessage
id='admin.sidebar.signUp'
defaultMessage='Sign Up'
/>
}
/>
<AdminSidebarSection
name='password'
title={
<FormattedMessage
id='admin.sidebar.password'
defaultMessage='Password'
/>
}
/>
<AdminSidebarSection
name='public_links'
title={
<FormattedMessage
id='admin.sidebar.publicLinks'
defaultMessage='Public Links'
/>
}
/>
<AdminSidebarSection
name='sessions'
title={
<FormattedMessage
id='admin.sidebar.sessions'
defaultMessage='Sessions'
/>
}
/>
<AdminSidebarSection
name='connections'
title={
<FormattedMessage
id='admin.sidebar.connections'
defaultMessage='Connections'
/>
}
/>
{clientVersions}
</AdminSidebarSection>
<AdminSidebarSection
name='notifications'
type='text'
title={
<FormattedMessage
id='admin.sidebar.notifications'
defaultMessage='Notifications'
/>
}
>
<AdminSidebarSection
name='notifications_email'
title={
<FormattedMessage
id='admin.sidebar.email'
defaultMessage='Email'
/>
}
/>
<AdminSidebarSection
name='push'
title={
<FormattedMessage
id='admin.sidebar.push'
defaultMessage='Mobile Push'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='integrations'
type='text'
title={
<FormattedMessage
id='admin.sidebar.integrations'
defaultMessage='Integrations'
/>
}
>
<AdminSidebarSection
name='custom'
title={
<FormattedMessage
id='admin.sidebar.customIntegrations'
defaultMessage='Custom Integrations'
/>
}
/>
<AdminSidebarSection
name='jira'
title={
<FormattedMessage
id='admin.sidebar.jira'
defaultMessage='JIRA (Beta)'
/>
}
/>
{webrtcSettings}
<AdminSidebarSection
name='external'
title={
<FormattedMessage
id='admin.sidebar.external'
defaultMessage='External Services'
/>
}
/>
{pluginSettings}
</AdminSidebarSection>
<AdminSidebarSection
name='files'
type='text'
title={
<FormattedMessage
id='admin.sidebar.files'
defaultMessage='Files'
/>
}
>
<AdminSidebarSection
key='storage'
name='storage'
title={
<FormattedMessage
id='admin.sidebar.storage'
defaultMessage='Storage'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='customization'
type='text'
title={
<FormattedMessage
id='admin.sidebar.customization'
defaultMessage='Customization'
/>
}
>
{customBranding}
<AdminSidebarSection
name='emoji'
title={
<FormattedMessage
id='admin.sidebar.emoji'
defaultMessage='Emoji'
/>
}
/>
<AdminSidebarSection
name='link_previews'
title={
<FormattedMessage
id='admin.sidebar.linkPreviews'
defaultMessage='Link Previews'
/>
}
/>
<AdminSidebarSection
name='legal_and_support'
title={
<FormattedMessage
id='admin.sidebar.legalAndSupport'
defaultMessage='Legal and Support'
/>
}
/>
<AdminSidebarSection
name='native_app_links'
title={
<FormattedMessage
id='admin.sidebar.nativeAppLinks'
defaultMessage='Mattermost App Links'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='advanced'
type='text'
title={
<FormattedMessage
id='admin.sidebar.advanced'
defaultMessage='Advanced'
/>
}
>
<AdminSidebarSection
name='rate'
title={
<FormattedMessage
id='admin.sidebar.rateLimiting'
defaultMessage='Rate Limiting'
/>
}
/>
<AdminSidebarSection
name='database'
title={
<FormattedMessage
id='admin.sidebar.database'
defaultMessage='Database'
/>
}
/>
{elasticSearchSettings}
<AdminSidebarSection
name='developer'
title={
<FormattedMessage
id='admin.sidebar.developer'
defaultMessage='Developer'
/>
}
/>
{clusterSettings}
{metricsSettings}
</AdminSidebarSection>
</AdminSidebarCategory>
{otherCategory}
</ul>
</div>
</div>
);
}
}

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

@@ -1,86 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Link} from 'react-router/es6';
export default class AdminSidebarCategory extends React.Component {
static get propTypes() {
return {
name: PropTypes.string,
title: PropTypes.node.isRequired,
icon: PropTypes.string.isRequired,
sectionClass: PropTypes.string,
parentLink: PropTypes.string,
children: PropTypes.node,
action: PropTypes.node
};
}
static get defaultProps() {
return {
parentLink: ''
};
}
static get contextTypes() {
return {
router: PropTypes.object.isRequired
};
}
render() {
let link = this.props.parentLink;
let title = (
<div className='category-title category-title--active'>
<i className={'category-icon fa ' + this.props.icon}/>
<span className='category-title__text'>
{this.props.title}
</span>
{this.props.action}
</div>
);
if (this.props.name) {
link += '/' + name;
title = (
<Link
to={link}
className='category-title'
activeClassName='category-title category-title--active'
>
{title}
</Link>
);
}
let clonedChildren = null;
if (this.props.children && this.context.router.isActive(link)) {
clonedChildren = (
<ul className={'sections ' + this.props.sectionClass}>
{
React.Children.map(this.props.children, (child) => {
if (child === null) {
return null;
}
return React.cloneElement(child, {
parentLink: link
});
})
}
</ul>
);
}
return (
<li className='sidebar-category'>
{title}
{clonedChildren}
</li>
);
}
}

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

@@ -1,69 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from 'stores/user_store.jsx';
import {Client4} from 'mattermost-redux/client';
import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class SidebarHeader extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
toggleDropdown = (e) => {
e.preventDefault();
if (this.refs.dropdown.blockToggle) {
this.refs.dropdown.blockToggle = false;
return;
}
$('.team__header').find('.dropdown-toggle').dropdown('toggle');
}
render() {
var me = UserStore.getCurrentUser();
var profilePicture = null;
if (!me) {
return null;
}
if (me.last_picture_update) {
profilePicture = (
<img
className='user__picture'
src={Client4.getProfilePictureUrl(me.id, me.last_picture_update)}
/>
);
}
return (
<div className='team__header theme'>
<a
href='#'
onClick={this.toggleDropdown}
>
{profilePicture}
<div className='header__info'>
<div className='team__name'>
<FormattedMessage
id='admin.sidebarHeader.systemConsole'
defaultMessage='System Console'
/>
</div>
<div className='user__name'>{'@' + me.username}</div>
</div>
</a>
<AdminNavbarDropdown ref='dropdown'/>
</div>
);
}
}

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

@@ -1,102 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Link} from 'react-router/es6';
import * as Utils from 'utils/utils.jsx';
export default class AdminSidebarSection extends React.Component {
static get propTypes() {
return {
name: PropTypes.string.isRequired,
title: PropTypes.node.isRequired,
type: PropTypes.string,
parentLink: PropTypes.string,
subsection: PropTypes.bool,
children: PropTypes.node,
action: PropTypes.node,
onlyActiveOnIndex: PropTypes.bool
};
}
static get defaultProps() {
return {
parentLink: '',
subsection: false,
children: [],
onlyActiveOnIndex: true
};
}
getLink() {
return this.props.parentLink + '/' + this.props.name;
}
render() {
const link = this.getLink();
let clonedChildren = null;
if (this.props.children) {
clonedChildren = (
<ul className='nav nav__sub-menu subsections'>
{
React.Children.map(this.props.children, (child) => {
if (child === null) {
return null;
}
return React.cloneElement(child, {
parentLink: link,
subsection: true
});
})
}
</ul>
);
}
let className = 'sidebar-section';
if (this.props.subsection) {
className += ' sidebar-subsection';
}
let sidebarItem = (
<Link
id={Utils.createSafeId(this.props.name)}
className={`${className}-title`}
activeClassName={`${className}-title ${className}-title--active`}
onlyActiveOnIndex={this.props.onlyActiveOnIndex}
onClick={this.handleClick}
to={link}
>
<span className={`${className}-title__text`}>
{this.props.title}
</span>
{this.props.action}
</Link>
);
if (this.props.type === 'text') {
sidebarItem = (
<div
className={`${className}-title`}
>
<span className={`${className}-title__text`}>
{this.props.title}
</span>
{this.props.action}
</div>
);
}
return (
<li className={className}>
{sidebarItem}
{clonedChildren}
</li>
);
}
}

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

@@ -1,101 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import AuditTable from 'components/audit_table.jsx';
import ComplianceReports from 'components/admin_console/compliance_reports';
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
export default class Audits extends React.PureComponent {
static propTypes = {
/*
* Array of audits to render
*/
audits: PropTypes.arrayOf(PropTypes.object).isRequired,
actions: PropTypes.shape({
/*
* Function to fetch audits
*/
getAudits: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.state = {
loadingAudits: true
};
}
componentDidMount() {
this.props.actions.getAudits().then(
() => this.setState({loadingAudits: false})
);
}
reload = () => {
this.setState({loadingAudits: true});
this.props.actions.getAudits().then(
() => this.setState({loadingAudits: false})
);
}
render() {
let content = null;
if (global.window.mm_license.IsLicensed !== 'true') {
return <div/>;
}
if (this.state.loadingAudits) {
content = <LoadingScreen/>;
} else {
content = (
<div style={{margin: '10px'}}>
<AuditTable
audits={this.props.audits}
showUserId={true}
showIp={true}
showSession={true}
/>
</div>
);
}
return (
<div>
<ComplianceReports/>
<div className='panel audit-panel'>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.audits.title'
defaultMessage='User Activity Logs'
/>
<button
type='submit'
className='btn btn-link pull-right'
onClick={this.reload}
>
<i className='fa fa-refresh'/>
<FormattedMessage
id='admin.audits.reload'
defaultMessage='Reload User Activity Logs'
/>
</button>
</h3>
<div className='audit-panel__table'>
{content}
</div>
</div>
</div>
);
}
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getAudits} from 'mattermost-redux/actions/admin';
import * as Selectors from 'mattermost-redux/selectors/entities/admin';
import Audits from './audits.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
audits: Object.values(Selectors.getAudits(state))
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getAudits
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Audits);

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

@@ -1,39 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
export default function Banner(props) {
let title = (
<FormattedMessage
id='admin.banner.heading'
defaultMessage='Note:'
/>
);
if (props.title) {
title = props.title;
}
return (
<div className='banner'>
<div className='banner__content'>
<h4 className='banner__heading'>
{title}
</h4>
<p>
{props.description}
</p>
</div>
</div>
);
}
Banner.defaultProps = {
};
Banner.propTypes = {
title: PropTypes.node,
description: PropTypes.node.isRequired
};

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

@@ -1,99 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Setting from './setting.jsx';
import {FormattedMessage} from 'react-intl';
import * as Utils from 'utils/utils.jsx';
export default class BooleanSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value === 'true');
}
render() {
let helpText;
if (this.props.disabled && this.props.disabledText) {
helpText = (
<div>
<span className='admin-console__disabled-text'>
{this.props.disabledText}
</span>
{this.props.helpText}
</div>
);
} else {
helpText = this.props.helpText;
}
return (
<Setting
label={this.props.label}
helpText={helpText}
>
<label className='radio-inline'>
<input
type='radio'
value='true'
id={Utils.createSafeId(this.props.id) + 'true'}
name={this.props.id}
checked={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{this.props.trueText}
</label>
<label className='radio-inline'>
<input
type='radio'
value='false'
id={Utils.createSafeId(this.props.id) + 'false'}
name={this.props.id}
checked={!this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{this.props.falseText}
</label>
</Setting>
);
}
}
BooleanSetting.defaultProps = {
trueText: (
<FormattedMessage
id='admin.true'
defaultMessage='true'
/>
),
falseText: (
<FormattedMessage
id='admin.false'
defaultMessage='false'
/>
),
disabled: false
};
BooleanSetting.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
trueText: PropTypes.node,
falseText: PropTypes.node,
disabled: PropTypes.bool.isRequired,
disabledText: PropTypes.node,
helpText: PropTypes.node.isRequired
};

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

@@ -1,213 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {Client4} from 'mattermost-redux/client';
import * as Utils from 'utils/utils.jsx';
import {uploadBrandImage} from 'actions/admin_actions.jsx';
import FormError from 'components/form_error.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
const HTTP_STATUS_OK = 200;
export default class BrandImageSetting extends React.PureComponent {
static propTypes = {
/*
* Set to disable the setting
*/
disabled: PropTypes.bool.isRequired
}
constructor(props) {
super(props);
this.handleImageChange = this.handleImageChange.bind(this);
this.handleImageSubmit = this.handleImageSubmit.bind(this);
this.state = {
brandImage: null,
brandImageExists: false,
brandImageTimestamp: Date.now(),
uploading: false,
uploadCompleted: false,
error: ''
};
}
componentWillMount() {
fetch(Client4.getBrandImageUrl(this.state.brandImageTimestamp)).then(
(resp) => {
if (resp.status === HTTP_STATUS_OK) {
this.setState({brandImageExists: true});
} else {
this.setState({brandImageExists: false});
}
}
);
}
componentDidUpdate() {
if (this.refs.image) {
const reader = new FileReader();
const img = this.refs.image;
reader.onload = (e) => {
$(img).attr('src', e.target.result);
};
reader.readAsDataURL(this.state.brandImage);
}
}
handleImageChange() {
const element = $(this.refs.fileInput);
if (element.prop('files').length > 0) {
this.setState({
brandImage: element.prop('files')[0]
});
}
}
handleImageSubmit(e) {
e.preventDefault();
if (!this.state.brandImage) {
return;
}
if (this.state.uploading) {
return;
}
$(this.refs.upload).button('loading');
this.setState({
uploading: true,
error: ''
});
uploadBrandImage(
this.state.brandImage,
() => {
$(this.refs.upload).button('complete');
this.setState({
brandImageExists: true,
brandImage: null,
brandImageTimestamp: Date.now(),
uploading: false
});
},
(err) => {
$(this.refs.upload).button('reset');
this.setState({
uploading: false,
error: err.message
});
}
);
}
render() {
let btnPrimaryClass = 'btn';
if (this.state.brandImage) {
btnPrimaryClass += ' btn-primary';
}
let letbtnDefaultClass = 'btn';
if (!this.props.disabled) {
letbtnDefaultClass += ' btn-default';
}
let img = null;
if (this.state.brandImage) {
img = (
<img
ref='image'
className='brand-img'
src=''
/>
);
} else if (this.state.brandImageExists) {
img = (
<img
className='brand-img'
src={Client4.getBrandImageUrl(this.state.brandImageTimestamp)}
/>
);
} else {
img = (
<p>
<FormattedMessage
id='admin.team.noBrandImage'
defaultMessage='No brand image uploaded'
/>
</p>
);
}
return (
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage
id='admin.team.brandImageTitle'
defaultMessage='Custom Brand Image:'
/>
</label>
<div className='col-sm-8'>
{img}
</div>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<div className='file__upload'>
<button
className={letbtnDefaultClass}
disabled={this.props.disabled}
>
<FormattedMessage
id='admin.team.chooseImage'
defaultMessage='Choose New Image'
/>
</button>
<input
ref='fileInput'
type='file'
accept='.jpg,.png,.bmp'
disabled={this.props.disabled}
onChange={this.handleImageChange}
/>
</div>
<button
className={btnPrimaryClass}
disabled={this.props.disabled || !this.state.brandImage}
onClick={this.handleImageSubmit}
id='upload-button'
ref='upload'
data-loading-text={'<span class=\'fa fa-refresh fa-rotate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')}
data-complete-text={'<span class=\'fa fa-check\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')}
>
<FormattedMessage
id='admin.team.upload'
defaultMessage='Upload'
/>
</button>
<br/>
<FormError error={this.state.error}/>
<p className='help-text no-margin'>
<FormattedHTMLMessage
id='admin.team.uploadDesc'
defaultMessage='Customize your user experience by adding a custom image to your login screen. See examples at <a href="http://docs.mattermost.com/administration/config-settings.html#custom-branding" target="_blank">docs.mattermost.com/administration/config-settings.html#custom-branding</a>.'
/>
</p>
</div>
</div>
);
}
}

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

@@ -1,169 +0,0 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ClientVersionsSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ClientRequirements.AndroidLatestVersion = this.state.androidLatestVersion;
config.ClientRequirements.AndroidMinVersion = this.state.androidMinVersion;
config.ClientRequirements.DesktopLatestVersion = this.state.desktopLatestVersion;
config.ClientRequirements.DesktopMinVersion = this.state.desktopMinVersion;
config.ClientRequirements.IosLatestVersion = this.state.iosLatestVersion;
config.ClientRequirements.IosMinVersion = this.state.iosMinVersion;
return config;
}
getStateFromConfig(config) {
return {
androidLatestVersion: config.ClientRequirements.AndroidLatestVersion,
androidMinVersion: config.ClientRequirements.AndroidMinVersion,
desktopLatestVersion: config.ClientRequirements.DesktopLatestVersion,
desktopMinVersion: config.ClientRequirements.DesktopMinVersion,
iosLatestVersion: config.ClientRequirements.IosLatestVersion,
iosMinVersion: config.ClientRequirements.IosMinVersion
};
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.client_versions'
defaultMessage='Client Versions'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup>
<TextSetting
id='androidLatestVersion'
label={
<FormattedMessage
id='admin.client_versions.androidLatestVersion'
defaultMessage='Latest Android Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.androidLatestVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.androidLatestVersionHelp'
defaultMessage='The latest released Android version'
/>
}
value={this.state.androidLatestVersion}
onChange={this.handleChange}
/>
<TextSetting
id='androidMinVersion'
label={
<FormattedMessage
id='admin.client_versions.androidMinVersion'
defaultMessage='Minimum Android Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.androidMinVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.androidMinVersionHelp'
defaultMessage='The minimum compliant Android version'
/>
}
value={this.state.androidMinVersion}
onChange={this.handleChange}
/>
<TextSetting
id='desktopLatestVersion'
label={
<FormattedMessage
id='admin.client_versions.desktopLatestVersion'
defaultMessage='Latest Desktop Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.desktopLatestVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.desktopLatestVersionHelp'
defaultMessage='The latest released Desktop version'
/>
}
value={this.state.desktopLatestVersion}
onChange={this.handleChange}
/>
<TextSetting
id='desktopMinVersion'
label={
<FormattedMessage
id='admin.client_versions.desktopMinVersion'
defaultMessage='Minimum Destop Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.desktopMinVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.desktopMinVersionHelp'
defaultMessage='The minimum compliant Desktop version'
/>
}
value={this.state.desktopMinVersion}
onChange={this.handleChange}
/>
<TextSetting
id='iosLatestVersion'
label={
<FormattedMessage
id='admin.client_versions.iosLatestVersion'
defaultMessage='Latest IOS Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.iosLatestVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.iosLatestVersionHelp'
defaultMessage='The latest released IOS version'
/>
}
value={this.state.iosLatestVersion}
onChange={this.handleChange}
/>
<TextSetting
id='iosMinVersion'
label={
<FormattedMessage
id='admin.client_versions.iosMinVersion'
defaultMessage='Minimum IOS Version'
/>
}
placeholder={Utils.localizeMessage('admin.client_versions.iosMinVersion', 'X.X.X')}
helpText={
<FormattedMessage
id='admin.client_versions.iosMinVersionHelp'
defaultMessage='The minimum compliant IOS version'
/>
}
value={this.state.iosMinVersion}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,273 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import TextSetting from './text_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import ClusterTableContainer from './cluster_table_container.jsx';
import * as Utils from 'utils/utils.jsx';
import {Client4} from 'mattermost-redux/client';
export default class ClusterSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.overrideHandleChange = this.overrideHandleChange.bind(this);
}
getConfigFromState(config) {
config.ClusterSettings.Enable = this.state.Enable;
config.ClusterSettings.ClusterName = this.state.ClusterName;
config.ClusterSettings.OverrideHostname = this.state.OverrideHostname;
config.ClusterSettings.UseIpAddress = this.state.UseIpAddress;
config.ClusterSettings.UseExperimentalGossip = this.state.UseExperimentalGossip;
config.ClusterSettings.ReadOnlyConfig = this.state.ReadOnlyConfig;
config.ClusterSettings.GossipPort = this.parseIntNonZero(this.state.GossipPort, 8074);
config.ClusterSettings.StreamingPort = this.parseIntNonZero(this.state.StreamingPort, 8075);
return config;
}
getStateFromConfig(config) {
const settings = config.ClusterSettings;
return {
Enable: settings.Enable,
ClusterName: settings.ClusterName,
OverrideHostname: settings.OverrideHostname,
UseIpAddress: settings.UseIpAddress,
UseExperimentalGossip: settings.UseExperimentalGossip,
ReadOnlyConfig: settings.ReadOnlyConfig,
GossipPort: settings.GossipPort,
StreamingPort: settings.StreamingPort,
showWarning: false
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.advance.cluster'
defaultMessage='High Availability'
/>
);
}
overrideHandleChange(id, value) {
this.setState({
showWarning: true
});
this.handleChange(id, value);
}
renderSettings() {
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Cluster === 'true';
if (!licenseEnabled) {
return null;
}
var configLoadedFromCluster = null;
if (Client4.clusterId) {
configLoadedFromCluster = (
<div
style={{marginBottom: '10px'}}
className='alert alert-warning'
>
<i className='fa fa-warning'/>
<FormattedHTMLMessage
id='admin.cluster.loadedFrom'
defaultMessage='This configuration file was loaded from Node ID {clusterId}. Please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> if you are accessing the System Console through a load balancer and experiencing issues.'
values={{
clusterId: Client4.clusterId
}}
/>
</div>
);
}
var warning = null;
if (this.state.showWarning) {
warning = (
<div
style={{marginBottom: '10px'}}
className='alert alert-warning'
>
<i className='fa fa-warning'/>
<FormattedHTMLMessage
id='admin.cluster.should_not_change'
defaultMessage='WARNING: These settings may not sync with the other servers in the cluster. High Availability inter-node communication will not start until you modify the config.json to be identical on all servers and restart Mattermost. Please see the <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> on how to add or remove a server from the cluster. If you are accessing the System Console through a load balancer and experiencing issues, please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a>.'
/>
</div>
);
}
var clusterTableContainer = null;
if (this.state.Enable) {
clusterTableContainer = (<ClusterTableContainer/>);
}
return (
<SettingsGroup>
{configLoadedFromCluster}
{clusterTableContainer}
<div className='banner'>
<FormattedMessage
id='admin.cluster.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect. When High Availability mode is enabled, the System Console is set to read-only and can only be changed from the configuration file unless ReadOnlyConfig is disabled in the configuration file.'
/>
</div>
{warning}
<BooleanSetting
id='Enable'
label={
<FormattedMessage
id='admin.cluster.enableTitle'
defaultMessage='Enable High Availability Mode:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.cluster.enableDescription'
defaultMessage='When true, Mattermost will run in High Availability mode. Please see <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> to learn more about configuring High Availability for Mattermost.'
/>
}
value={this.state.Enable}
onChange={this.overrideHandleChange}
/>
<TextSetting
id='ClusterName'
label={
<FormattedMessage
id='admin.cluster.ClusterName'
defaultMessage='Cluster Name:'
/>
}
placeholder={Utils.localizeMessage('admin.cluster.ClusterNameEx', 'Ex "Production" or "Staging"')}
helpText={
<FormattedMessage
id='admin.cluster.ClusterNameDesc'
defaultMessage='The cluster to join by name. Only nodes with the same cluster name will join together. This is to support Blue-Green deployments or staging pointing to the same database.'
/>
}
value={this.state.ClusterName}
onChange={this.overrideHandleChange}
/>
<TextSetting
id='OverrideHostname'
label={
<FormattedMessage
id='admin.cluster.OverrideHostname'
defaultMessage='Override Hostname:'
/>
}
placeholder={Utils.localizeMessage('admin.cluster.OverrideHostnameEx', 'Ex "app-server-01"')}
helpText={
<FormattedMessage
id='admin.cluster.OverrideHostnameDesc'
defaultMessage='The default value of <blank> will attempt to get the Hostname from the OS or use the IP Address. You can override the hostname of this server with this property. It is not recommended to override the Hostname unless needed. This property can also be set to a specific IP Address if needed.'
/>
}
value={this.state.OverrideHostname}
onChange={this.overrideHandleChange}
/>
<BooleanSetting
id='UseIpAddress'
label={
<FormattedMessage
id='admin.cluster.UseIpAddress'
defaultMessage='Use IP Address:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.cluster.UseIpAddressDesc'
defaultMessage='When true, the cluster will attempt to communicate via IP Address vs using the hostname.'
/>
}
value={this.state.UseIpAddress}
onChange={this.overrideHandleChange}
/>
<BooleanSetting
id='UseExperimentalGossip'
label={
<FormattedMessage
id='admin.cluster.UseExperimentalGossip'
defaultMessage='Use Experimental Gossip:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.cluster.UseExperimentalGossipDesc'
defaultMessage='When true, the server will attempt to communicate via the gossip protocol over the gossip port. When false the server will attempt to communicate over the streaming port. When false the gossip port and protocol are still used to determine cluster health.'
/>
}
value={this.state.UseExperimentalGossip}
onChange={this.overrideHandleChange}
/>
<BooleanSetting
id='ReadOnlyConfig'
label={
<FormattedMessage
id='admin.cluster.ReadOnlyConfig'
defaultMessage='Read Only Config:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.cluster.ReadOnlyConfigDesc'
defaultMessage='When true, the server will reject changes made to the configuration file from the system console. When running in production it is recommened to set this to true.'
/>
}
value={this.state.ReadOnlyConfig}
onChange={this.overrideHandleChange}
/>
<TextSetting
id='GossipPort'
label={
<FormattedMessage
id='admin.cluster.GossipPort'
defaultMessage='Gossip Port:'
/>
}
placeholder={Utils.localizeMessage('admin.cluster.GossipPortEx', 'Ex "8074"')}
helpText={
<FormattedMessage
id='admin.cluster.GossipPortDesc'
defaultMessage='The port used for the gossip protocol. Both UDP and TCP should abe allowed on this port.'
/>
}
value={this.state.GossipPort}
onChange={this.overrideHandleChange}
/>
<TextSetting
id='StreamingPort'
label={
<FormattedMessage
id='admin.cluster.StreamingPort'
defaultMessage='Streaming Port:'
/>
}
placeholder={Utils.localizeMessage('admin.cluster.StreamingPortEx', 'Ex "8075"')}
helpText={
<FormattedMessage
id='admin.cluster.StreamingPortDesc'
defaultMessage='The port used for streaming data between servers.'
/>
}
value={this.state.StreamingPort}
onChange={this.overrideHandleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,172 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import * as Utils from 'utils/utils.jsx';
import statusGreen from 'images/status_green.png';
import statusYellow from 'images/status_yellow.png';
export default class ClusterTable extends React.Component {
static propTypes = {
clusterInfos: PropTypes.array.isRequired,
reload: PropTypes.func.isRequired
}
render() {
var versionMismatch = (
<img
className='cluster-status'
src={statusGreen}
/>
);
var configMismatch = (
<img
className='cluster-status'
src={statusGreen}
/>
);
var version = '';
var configHash = '';
var singleItem = false;
if (this.props.clusterInfos.length) {
version = this.props.clusterInfos[0].version;
configHash = this.props.clusterInfos[0].config_hash;
singleItem = this.props.clusterInfos.length === 1;
}
this.props.clusterInfos.map((clusterInfo) => {
if (clusterInfo.version !== version) {
versionMismatch = (
<img
className='cluster-status'
src={statusYellow}
/>
);
}
if (clusterInfo.config_hash !== configHash) {
configMismatch = (
<img
className='cluster-status'
src={statusYellow}
/>
);
}
return null;
});
var items = this.props.clusterInfos.map((clusterInfo) => {
var status = null;
if (clusterInfo.hostname === '') {
clusterInfo.hostname = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
}
if (clusterInfo.version === '') {
clusterInfo.version = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
}
if (clusterInfo.config_hash === '') {
clusterInfo.config_hash = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
}
if (singleItem) {
status = (
<img
className='cluster-status'
src={statusYellow}
/>
);
} else {
status = (
<img
className='cluster-status'
src={statusGreen}
/>
);
}
return (
<tr key={clusterInfo.ipaddress}>
<td style={{whiteSpace: 'nowrap'}}>{status}</td>
<td style={{whiteSpace: 'nowrap'}}>{clusterInfo.hostname}</td>
<td style={{whiteSpace: 'nowrap'}}>{versionMismatch} {clusterInfo.version}</td>
<td style={{whiteSpace: 'nowrap'}}><div className='config-hash'>{configMismatch} {clusterInfo.config_hash}</div></td>
<td style={{whiteSpace: 'nowrap'}}>{clusterInfo.ipaddress}</td>
</tr>
);
});
return (
<div
className='cluster-panel__table'
style={{
margin: '10px',
marginBottom: '30px'
}}
>
<div className='text-right'>
<button
type='submit'
className='btn btn-link'
onClick={this.props.reload}
>
<i className='fa fa-refresh'/>
<FormattedMessage
id='admin.cluster.status_table.reload'
defaultMessage=' Reload Cluster Status'
/>
</button>
</div>
<table className='table'>
<thead>
<tr>
<th>
<FormattedMessage
id='admin.cluster.status_table.status'
defaultMessage='Status'
/>
</th>
<th>
<FormattedMessage
id='admin.cluster.status_table.hostname'
defaultMessage='Hostname'
/>
</th>
<th>
<FormattedMessage
id='admin.cluster.status_table.version'
defaultMessage='Version'
/>
</th>
<th>
<FormattedMessage
id='admin.cluster.status_table.config_hash'
defaultMessage='Config File MD5'
/>
</th>
<th>
<FormattedMessage
id='admin.cluster.status_table.url'
defaultMessage='Gossip Address'
/>
</th>
</tr>
</thead>
<tbody>
{items}
</tbody>
</table>
</div>
);
}
}

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

@@ -1,72 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import ClusterTable from './cluster_table.jsx';
import LoadingScreen from '../loading_screen.jsx';
import {getClusterStatus} from 'actions/admin_actions.jsx';
export default class ClusterTableContainer extends React.Component {
constructor(props) {
super(props);
this.load = this.load.bind(this);
this.reload = this.reload.bind(this);
this.interval = null;
this.state = {
clusterInfos: null
};
}
load() {
getClusterStatus(
(data) => {
this.setState({
clusterInfos: data
});
},
null
);
}
componentWillMount() {
this.load();
// reload the cluster status every 15 seconds
this.interval = setInterval(this.load, 15000);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
reload(e) {
if (e) {
e.preventDefault();
}
this.setState({
clusterInfos: null
});
this.load();
}
render() {
if (this.state.clusterInfos == null) {
return (<LoadingScreen/>);
}
return (
<ClusterTable
clusterInfos={this.state.clusterInfos}
reload={this.reload}
/>
);
}
}

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

@@ -1,119 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Setting from './setting.jsx';
import React from 'react';
import PropTypes from 'prop-types';
import {ChromePicker} from 'react-color';
export default class ColorSetting extends React.PureComponent {
static propTypes = {
/*
* The unique identifer for the admin console setting
*/
id: PropTypes.string.isRequired,
/*
* The text/jsx display name for the setting
*/
label: PropTypes.node.isRequired,
/*
* The text/jsx help text to display underneath the setting
*/
helpText: PropTypes.node,
/*
* The hex color value
*/
value: PropTypes.string.isRequired,
/*
* Function called when the input changes
*/
onChange: PropTypes.func,
/*
* Set to disable the setting
*/
disabled: PropTypes.bool
}
constructor(props) {
super(props);
this.state = {
showPicker: false
};
}
componentDidMount() {
document.addEventListener('click', this.closePicker);
}
componentWillUnmount() {
document.removeEventListener('click', this.closePicker);
}
handleChange = (color) => {
this.props.onChange(this.props.id, color.hex);
}
togglePicker = () => {
if (this.props.disabled) {
this.setState({showPicker: false});
}
this.setState({showPicker: !this.state.showPicker});
}
closePicker = (e) => {
if (!e.target.closest('.picker-' + this.props.id)) {
this.setState({showPicker: false});
}
}
onTextInput = (e) => {
this.props.onChange(this.props.id, e.target.value);
}
render() {
let picker;
if (this.state.showPicker) {
picker = (
<div className={'color-picker__popover picker-' + this.props.id}>
<ChromePicker
color={this.props.value}
onChange={this.handleChange}
/>
</div>
);
}
return (
<Setting
label={this.props.label}
helpText={this.props.helpText}
inputId={this.props.id}
>
<div className='input-group color-picker colorpicker-element'>
<input
type='text'
className='form-control'
value={this.props.value}
onChange={this.onTextInput}
disabled={this.props.disabled}
/>
<span
className={'input-group-addon picker-' + this.props.id}
onClick={this.togglePicker}
>
<i style={{backgroundColor: this.props.value}}/>
</span>
{picker}
</div>
</Setting>
);
}
}

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

@@ -1,394 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import * as Utils from 'utils/utils.jsx';
import UserStore from 'stores/user_store.jsx';
import {Client4} from 'mattermost-redux/client';
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
export default class ComplianceReports extends React.PureComponent {
static propTypes = {
/*
* Set if compliance reports are enabled in the config
*/
enabled: PropTypes.bool.isRequired,
/*
* Array of reports to render
*/
reports: PropTypes.arrayOf(PropTypes.object).isRequired,
/*
* Error message to display
*/
serverError: PropTypes.string,
actions: PropTypes.shape({
/*
* Function to get compliance reports
*/
getComplianceReports: PropTypes.func.isRequired,
/*
* Function to save compliance reports
*/
createComplianceReport: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.state = {
loadingReports: true
};
}
componentDidMount() {
if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
return;
}
this.props.actions.getComplianceReports().then(
() => this.setState({loadingReports: false})
);
}
reload = () => {
this.setState({loadingReports: true});
this.props.actions.getComplianceReports().then(
() => this.setState({loadingReports: false})
);
}
runReport = (e) => {
e.preventDefault();
this.setState({runningReport: true});
const job = {};
job.desc = this.refs.desc.value;
job.emails = this.refs.emails.value;
job.keywords = this.refs.keywords.value;
job.start_at = Date.parse(this.refs.from.value);
job.end_at = Date.parse(this.refs.to.value);
this.props.actions.createComplianceReport(job).then(
(data) => {
if (data) {
this.refs.emails.value = '';
this.refs.keywords.value = '';
this.refs.desc.value = '';
this.refs.from.value = '';
this.refs.to.value = '';
}
this.setState({runningReport: false});
}
);
}
getDateTime(millis) {
const date = new Date(millis);
return (
<span style={{whiteSpace: 'nowrap'}}>
<FormattedDate
value={date}
day='2-digit'
month='short'
year='numeric'
/>
{' - '}
<FormattedTime
value={date}
hour='2-digit'
minute='2-digit'
/>
</span>
);
}
render() {
if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
return <div/>;
}
let content = null;
if (this.state.loadingReports) {
content = <LoadingScreen/>;
} else {
var list = [];
for (var i = 0; i < this.props.reports.length; i++) {
const report = this.props.reports[i];
let params = '';
if (report.type === 'adhoc') {
params = (
<span>
<FormattedMessage
id='admin.compliance_reports.from'
defaultMessage='From:'
/>{' '}{this.getDateTime(report.start_at)}
<br/>
<FormattedMessage
id='admin.compliance_reports.to'
defaultMessage='To:'
/>{' '}{this.getDateTime(report.end_at)}
<br/>
<FormattedMessage
id='admin.compliance_reports.emails'
defaultMessage='Emails:'
/>{' '}{report.emails}
<br/>
<FormattedMessage
id='admin.compliance_reports.keywords'
defaultMessage='Keywords:'
/>{' '}{report.keywords}
</span>);
}
let download = '';
if (report.status === 'finished') {
download = (
<a href={`${Client4.getBaseRoute()}/compliance/reports/${report.id}/download`}>
<FormattedMessage
id='admin.compliance_table.download'
defaultMessage='Download'
/>
</a>
);
}
let status = report.status;
if (report.status === 'finished') {
status = (
<span style={{color: 'green'}}>{report.status}</span>
);
}
if (report.status === 'failed') {
status = (
<span style={{color: 'red'}}>{report.status}</span>
);
}
let user = report.user_id;
const profile = UserStore.getProfile(report.user_id);
if (profile) {
user = profile.email;
}
list[i] = (
<tr key={report.id}>
<td style={{whiteSpace: 'nowrap'}}>{download}</td>
<td>{this.getDateTime(report.create_at)}</td>
<td>{status}</td>
<td>{report.count}</td>
<td>{report.type}</td>
<td style={{whiteSpace: 'nowrap'}}>{report.desc}</td>
<td>{user}</td>
<td style={{whiteSpace: 'nowrap'}}>{params}</td>
</tr>
);
}
content = (
<div style={{margin: '10px'}}>
<table className='table'>
<thead>
<tr>
<th/>
<th>
<FormattedMessage
id='admin.compliance_table.timestamp'
defaultMessage='Timestamp'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.status'
defaultMessage='Status'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.records'
defaultMessage='Records'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.type'
defaultMessage='Type'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.desc'
defaultMessage='Description'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.userId'
defaultMessage='Requested By'
/>
</th>
<th>
<FormattedMessage
id='admin.compliance_table.params'
defaultMessage='Params'
/>
</th>
</tr>
</thead>
<tbody>
{list}
</tbody>
</table>
</div>
);
}
let serverError = '';
if (this.props.serverError) {
serverError = (
<div
className='form-group has-error'
style={{marginTop: '10px'}}
>
<label className='control-label'>{this.props.serverError}</label>
</div>
);
}
return (
<div className='panel compliance-panel'>
<h3>
<FormattedMessage
id='admin.compliance_reports.title'
defaultMessage='Compliance Reports'
/>
</h3>
<div className='row'>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<FormattedMessage
id='admin.compliance_reports.desc'
defaultMessage='Job Name:'
/>
</label>
<input
type='text'
className='form-control'
id='desc'
ref='desc'
placeholder={Utils.localizeMessage('admin.compliance_reports.desc_placeholder', 'E.g. "Audit 445 for HR"')}
/>
</div>
<div className='col-sm-3 col-md-2 form-group'>
<label>
<FormattedMessage
id='admin.compliance_reports.from'
defaultMessage='From:'
/>
</label>
<input
type='text'
className='form-control'
id='from'
ref='from'
placeholder={Utils.localizeMessage('admin.compliance_reports.from_placeholder', 'E.g. "2016-03-11"')}
/>
</div>
<div className='col-sm-3 col-md-2 form-group'>
<label>
<FormattedMessage
id='admin.compliance_reports.to'
defaultMessage='To:'
/>
</label>
<input
type='text'
className='form-control'
id='to'
ref='to'
placeholder={Utils.localizeMessage('admin.compliance_reports.to_placeholder', 'E.g. "2016-03-15"')}
/>
</div>
</div>
<div className='row'>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<FormattedMessage
id='admin.compliance_reports.emails'
defaultMessage='Emails:'
/>
</label>
<input
type='text'
className='form-control'
id='emails'
ref='emails'
placeholder={Utils.localizeMessage('admin.compliance_reports.emails_placeholder', 'E.g. "bill@example.com, bob@example.com"')}
/>
</div>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<FormattedMessage
id='admin.compliance_reports.keywords'
defaultMessage='Keywords:'
/>
</label>
<input
type='text'
className='form-control'
id='keywords'
ref='keywords'
placeholder={Utils.localizeMessage('admin.compliance_reports.keywords_placeholder', 'E.g. "shorting stock"')}
/>
</div>
</div>
<div className='clearfix'>
<button
id='run-button'
type='submit'
className='btn btn-primary'
onClick={this.runReport}
>
<FormattedMessage
id='admin.compliance_reports.run'
defaultMessage='Run Compliance Report'
/>
</button>
</div>
{serverError}
<div className='text-right'>
<button
type='submit'
className='btn btn-link'
disabled={this.state.runningReport}
onClick={this.reload}
>
<i className='fa fa-refresh'/>
<FormattedMessage
id='admin.compliance_reports.reload'
defaultMessage='Reload Completed Compliance Reports'
/>
</button>
</div>
<div className='compliance-panel__table'>
{content}
</div>
</div>
);
}
}

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

@@ -1,46 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getComplianceReports, createComplianceReport} from 'mattermost-redux/actions/admin';
import {getComplianceReports as selectComplianceReports, getConfig} from 'mattermost-redux/selectors/entities/admin';
import ComplianceReports from './compliance_reports.jsx';
function mapStateToProps(state, ownProps) {
let enabled = false;
const config = getConfig(state);
if (config && config.ComplianceSettings) {
enabled = config.ComplianceSettings.Enable;
}
let serverError;
const error = state.requests.admin.createCompliance.error;
if (error) {
serverError = error.message;
}
const reports = Object.values(selectComplianceReports(state)).sort((a, b) => {
return b.create_at - a.create_at;
});
return {
...ownProps,
enabled,
reports,
serverError
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getComplianceReports,
createComplianceReport
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ComplianceReports);

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

@@ -1,126 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ComplianceSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ComplianceSettings.Enable = this.state.enable;
config.ComplianceSettings.Directory = this.state.directory;
config.ComplianceSettings.EnableDaily = this.state.enableDaily;
return config;
}
getStateFromConfig(config) {
return {
enable: config.ComplianceSettings.Enable,
directory: config.ComplianceSettings.Directory,
enableDaily: config.ComplianceSettings.EnableDaily
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.compliance.title'
defaultMessage='Compliance Settings'
/>
);
}
renderSettings() {
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true';
let bannerContent;
if (!licenseEnabled) {
bannerContent = (
<div className='banner warning'>
<div className='banner__content'>
<FormattedHTMLMessage
id='admin.compliance.noLicense'
defaultMessage='<h4 class="banner__heading">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href="http://mattermost.com"target="_blank">here</a> for information and pricing on enterprise licenses.</p>'
/>
</div>
</div>
);
}
return (
<SettingsGroup>
{bannerContent}
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.compliance.enableTitle'
defaultMessage='Enable Compliance Reporting:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.compliance.enableDesc'
defaultMessage='When true, Mattermost allows compliance reporting from the <strong>Compliance and Auditing</strong> tab. See <a href="https://docs.mattermost.com/administration/compliance.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enable}
onChange={this.handleChange}
disabled={!licenseEnabled}
/>
<TextSetting
id='directory'
label={
<FormattedMessage
id='admin.compliance.directoryTitle'
defaultMessage='Compliance Report Directory:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.compliance.directoryDescription'
defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
/>
}
value={this.state.directory}
onChange={this.handleChange}
disabled={!licenseEnabled || !this.state.enable}
/>
<BooleanSetting
id='enableDaily'
label={
<FormattedMessage
id='admin.compliance.enableDailyTitle'
defaultMessage='Enable Daily Report:'
/>
}
helpText={
<FormattedMessage
id='admin.compliance.enableDailyDesc'
defaultMessage='When true, Mattermost will generate a daily compliance report.'
/>
}
value={this.state.enableDaily}
onChange={this.handleChange}
disabled={!licenseEnabled || !this.state.enable}
/>
</SettingsGroup>
);
}
}

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

@@ -1,356 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import ErrorStore from 'stores/error_store.jsx';
import {ErrorBarTypes} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import {invalidateAllCaches, reloadConfig} from 'actions/admin_actions.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {ConnectionSecurityDropdownSettingWebserver} from './connection_security_dropdown_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import RequestButton from './request_button/request_button';
import TextSetting from './text_setting.jsx';
import WebserverModeDropdownSetting from './webserver_mode_dropdown_setting.jsx';
export default class ConfigurationSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSaved = this.handleSaved.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
componentWillReceiveProps(nextProps) {
// special case for this page since we don't update AdminSettings components when the
// stored config changes, but we want this page to update when you reload the config
this.setState(this.getStateFromConfig(nextProps.config));
}
getConfigFromState(config) {
config.ServiceSettings.SiteURL = this.state.siteURL;
config.ServiceSettings.ListenAddress = this.state.listenAddress;
config.ServiceSettings.WebserverMode = this.state.webserverMode;
config.ServiceSettings.ConnectionSecurity = this.state.connectionSecurity;
config.ServiceSettings.TLSCertFile = this.state.TLSCertFile;
config.ServiceSettings.TLSKeyFile = this.state.TLSKeyFile;
config.ServiceSettings.UseLetsEncrypt = this.state.useLetsEncrypt;
config.ServiceSettings.LetsEncryptCertificateCacheFile = this.state.letsEncryptCertificateCacheFile;
config.ServiceSettings.Forward80To443 = this.state.forward80To443;
config.ServiceSettings.ReadTimeout = this.parseIntNonZero(this.state.readTimeout);
config.ServiceSettings.WriteTimeout = this.parseIntNonZero(this.state.writeTimeout);
config.ServiceSettings.EnableAPIv3 = this.state.enableAPIv3;
return config;
}
getStateFromConfig(config) {
return {
siteURL: config.ServiceSettings.SiteURL,
listenAddress: config.ServiceSettings.ListenAddress,
webserverMode: config.ServiceSettings.WebserverMode,
connectionSecurity: config.ServiceSettings.ConnectionSecurity,
TLSCertFile: config.ServiceSettings.TLSCertFile,
TLSKeyFile: config.ServiceSettings.TLSKeyFile,
useLetsEncrypt: config.ServiceSettings.UseLetsEncrypt,
letsEncryptCertificateCacheFile: config.ServiceSettings.LetsEncryptCertificateCacheFile,
forward80To443: config.ServiceSettings.Forward80To443,
readTimeout: config.ServiceSettings.ReadTimeout,
writeTimeout: config.ServiceSettings.WriteTimeout,
enableAPIv3: config.ServiceSettings.EnableAPIv3
};
}
handleSaved(newConfig) {
if (newConfig.ServiceSettings.SiteURL) {
ErrorStore.clearError(ErrorBarTypes.SITE_URL);
}
}
renderTitle() {
return (
<FormattedMessage
id='admin.general.configuration'
defaultMessage='Configuration'
/>
);
}
renderSettings() {
const reloadConfigurationHelpText = (
<FormattedMessage
id='admin.reload.reloadDescription'
defaultMessage='Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {featureName} feature to load the new settings while the server is running. The administrator should then use the {recycleDatabaseConnections} feature to recycle the database connections based on the new settings.'
values={{
featureName: (
<b>
<FormattedMessage
id='admin.reload.reloadDescription.featureName'
defaultMessage='Reload Configuration from Disk'
/>
</b>
),
recycleDatabaseConnections: (
<a href='../advanced/database'>
<b>
<FormattedMessage
id='admin.reload.reloadDescription.recycleDatabaseConnections'
defaultMessage='Database > Recycle Database Connections'
/>
</b>
</a>
)
}}
/>
);
let reloadConfigButton = <div/>;
if (global.window.mm_license.IsLicensed === 'true') {
reloadConfigButton = (
<RequestButton
requestAction={reloadConfig}
helpText={reloadConfigurationHelpText}
buttonText={
<FormattedMessage
id='admin.reload.button'
defaultMessage='Reload Configuration From Disk'
/>
}
showSuccessMessage={false}
errorMessage={{
id: 'admin.reload.reloadFail',
defaultMessage: 'Reload unsuccessful: {error}'
}}
/>
);
}
return (
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<FormattedMessage
id='admin.rate.noteDescription'
defaultMessage='Changing properties other than Site URL in this section will require a server restart before taking effect.'
/>
</div>
</div>
<TextSetting
id='siteURL'
label={
<FormattedMessage
id='admin.service.siteURL'
defaultMessage='Site URL:'
/>
}
placeholder={Utils.localizeMessage('admin.service.siteURLExample', 'Ex "https://mattermost.example.com:1234"')}
helpText={
<FormattedMessage
id='admin.service.siteURLDescription'
defaultMessage='The URL that users will use to access Mattermost. Standard ports, such as 80 and 443, can be omitted, but non-standard ports are required. For example: http://mattermost.example.com:8065. This setting is required.'
/>
}
value={this.state.siteURL}
onChange={this.handleChange}
/>
<TextSetting
id='listenAddress'
label={
<FormattedMessage
id='admin.service.listenAddress'
defaultMessage='Listen Address:'
/>
}
placeholder={Utils.localizeMessage('admin.service.listenExample', 'Ex ":8065"')}
helpText={
<FormattedMessage
id='admin.service.listenDescription'
defaultMessage='The address and port to which to bind and listen. Specifying ":8065" will bind to all network interfaces. Specifying "127.0.0.1:8065" will only bind to the network interface having that IP address. If you choose a port of a lower level (called "system ports" or "well-known ports", in the range of 0-1023), you must have permissions to bind to that port. On Linux you can use: "sudo setcap cap_net_bind_service=+ep ./bin/platform" to allow Mattermost to bind to well-known ports.'
/>
}
value={this.state.listenAddress}
onChange={this.handleChange}
/>
<ConnectionSecurityDropdownSettingWebserver
value={this.state.connectionSecurity}
onChange={this.handleChange}
disabled={false}
/>
<TextSetting
id='TLSCertFile'
label={
<FormattedMessage
id='admin.service.tlsCertFile'
defaultMessage='TLS Certificate File:'
/>
}
helpText={
<FormattedMessage
id='admin.service.tlsCertFileDescription'
defaultMessage='The certificate file to use.'
/>
}
disabled={this.state.useLetsEncrypt}
value={this.state.TLSCertFile}
onChange={this.handleChange}
/>
<TextSetting
id='TLSKeyFile'
label={
<FormattedMessage
id='admin.service.tlsKeyFile'
defaultMessage='TLS Key File:'
/>
}
helpText={
<FormattedMessage
id='admin.service.tlsKeyFileDescription'
defaultMessage='The private key file to use.'
/>
}
disabled={this.state.useLetsEncrypt}
value={this.state.TLSKeyFile}
onChange={this.handleChange}
/>
<BooleanSetting
id='useLetsEncrypt'
label={
<FormattedMessage
id='admin.service.useLetsEncrypt'
defaultMessage="Use Let's Encrypt:"
/>
}
helpText={
<FormattedMessage
id='admin.service.useLetsEncryptDescription'
defaultMessage="Enable the automatic retreval of certificates from the Let's Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains."
/>
}
value={this.state.useLetsEncrypt}
onChange={this.handleChange}
/>
<TextSetting
id='letsEncryptCertificateCacheFile'
label={
<FormattedMessage
id='admin.service.letsEncryptCertificateCacheFile'
defaultMessage="Let's Encrypt Certificate Cache File:"
/>
}
helpText={
<FormattedMessage
id='admin.service.letsEncryptCertificateCacheFileDescription'
defaultMessage="Certificates retrieved and other data about the Let's Encrypt service will be stored in this file."
/>
}
disabled={!this.state.useLetsEncrypt}
value={this.state.letsEncryptCertificateCacheFile}
onChange={this.handleChange}
/>
<BooleanSetting
id='forward80To443'
label={
<FormattedMessage
id='admin.service.forward80To443'
defaultMessage='Forward port 80 to 443:'
/>
}
helpText={
<FormattedMessage
id='admin.service.forward80To443Description'
defaultMessage='Forwards all insecure traffic from port 80 to secure port 443'
/>
}
value={this.state.forward80To443}
onChange={this.handleChange}
/>
<TextSetting
id='readTimeout'
label={
<FormattedMessage
id='admin.service.readTimeout'
defaultMessage='Read Timeout:'
/>
}
helpText={
<FormattedMessage
id='admin.service.readTimeoutDescription'
defaultMessage='Maximum time allowed from when the connection is accepted to when the request body is fully read.'
/>
}
value={this.state.readTimeout}
onChange={this.handleChange}
/>
<TextSetting
id='writeTimeout'
label={
<FormattedMessage
id='admin.service.writeTimeout'
defaultMessage='Write Timeout:'
/>
}
helpText={
<FormattedMessage
id='admin.service.writeTimeoutDescription'
defaultMessage='If using HTTP (insecure), this is the maximum time allowed from the end of reading the request headers until the response is written. If using HTTPS, it is the total time from when the connection is accepted until the response is written.'
/>
}
value={this.state.writeTimeout}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableAPIv3'
label={
<FormattedMessage
id='admin.service.enableAPIv3'
defaultMessage='Allow use of API v3 endpoints:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.enableAPIv3Description'
defaultMessage='Set to false to disable all version 3 endpoints of the REST API. Integrations that rely on API v3 will fail and can then be identified for migration to API v4. API v3 is deprecated and will be removed in the near future. See <a href="https://api.mattermost.com" target="_blank">https://api.mattermost.com</a> for details.'
/>
}
value={this.state.enableAPIv3}
onChange={this.handleChange}
/>
<WebserverModeDropdownSetting
value={this.state.webserverMode}
onChange={this.handleChange}
disabled={false}
/>
{reloadConfigButton}
<RequestButton
requestAction={invalidateAllCaches}
helpText={
<FormattedMessage
id='admin.purge.purgeDescription'
defaultMessage='This will purge all the in-memory caches for things like sessions, accounts, channels, etc. Deployments using High Availability will attempt to purge all the servers in the cluster. Purging the caches may adversely impact performance.'
/>
}
buttonText={
<FormattedMessage
id='admin.purge.button'
defaultMessage='Purge All Caches'
/>
}
showSuccessMessage={false}
includeDetailedError={true}
errorMessage={{
id: 'admin.purge.purgeFail',
defaultMessage: 'Purging unsuccessful: {error}'
}}
/>
</SettingsGroup>
);
}
}

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

@@ -1,191 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage} from 'react-intl';
const SECTION_NONE = (
<tr>
<td>
<FormattedMessage
id='admin.connectionSecurityNone'
defaultMessage='None'
/>
</td>
<td>
<FormattedMessage
id='admin.connectionSecurityNoneDescription'
defaultMessage='Mattermost will connect over an insecure connection.'
/>
</td>
</tr>
);
const SECTION_TLS = (
<tr>
<td>
<FormattedMessage
id='admin.connectionSecurityTls'
defaultMessage='TLS'
/>
</td>
<td>
<FormattedMessage
id='admin.connectionSecurityTlsDescription'
defaultMessage='Encrypts the communication between Mattermost and your server.'
/>
</td>
</tr>
);
const SECTION_STARTTLS = (
<tr>
<td>
<FormattedMessage
id='admin.connectionSecurityStart'
defaultMessage='STARTTLS'
/>
</td>
<td>
<FormattedMessage
id='admin.connectionSecurityStartDescription'
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
/>
</td>
</tr>
);
const CONNECTION_SECURITY_HELP_TEXT_EMAIL = (
<table
className='table table-bordered table-margin--none'
cellPadding='5'
>
<tbody>
{SECTION_NONE}
{SECTION_TLS}
{SECTION_STARTTLS}
</tbody>
</table>
);
const CONNECTION_SECURITY_HELP_TEXT_LDAP = (
<table
className='table table-bordered table-margin--none'
cellPadding='5'
>
<tbody>
{SECTION_NONE}
{SECTION_TLS}
{SECTION_STARTTLS}
</tbody>
</table>
);
const CONNECTION_SECURITY_HELP_TEXT_WEBSERVER = (
<table
className='table table-bordered table-margin--none'
cellPadding='5'
>
<tbody>
{SECTION_NONE}
{SECTION_TLS}
</tbody>
</table>
);
export function ConnectionSecurityDropdownSettingEmail(props) {
return (
<DropdownSetting
id='connectionSecurity'
values={[
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
{value: 'STARTTLS', text: Utils.localizeMessage('admin.connectionSecurityStart')}
]}
label={
<FormattedMessage
id='admin.connectionSecurityTitle'
defaultMessage='Connection Security:'
/>
}
value={props.value}
onChange={props.onChange}
disabled={props.disabled}
helpText={CONNECTION_SECURITY_HELP_TEXT_EMAIL}
/>
);
}
ConnectionSecurityDropdownSettingEmail.defaultProps = {
};
ConnectionSecurityDropdownSettingEmail.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
};
export function ConnectionSecurityDropdownSettingLdap(props) {
return (
<DropdownSetting
id='connectionSecurity'
values={[
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
{value: 'STARTTLS', text: Utils.localizeMessage('admin.connectionSecurityStart')}
]}
label={
<FormattedMessage
id='admin.connectionSecurityTitle'
defaultMessage='Connection Security:'
/>
}
value={props.value}
onChange={props.onChange}
disabled={props.disabled}
helpText={CONNECTION_SECURITY_HELP_TEXT_LDAP}
/>
);
}
ConnectionSecurityDropdownSettingLdap.defaultProps = {
};
ConnectionSecurityDropdownSettingLdap.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
};
export function ConnectionSecurityDropdownSettingWebserver(props) {
return (
<DropdownSetting
id='connectionSecurity'
values={[
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')}
]}
label={
<FormattedMessage
id='admin.connectionSecurityTitle'
defaultMessage='Connection Security:'
/>
}
value={props.value}
onChange={props.onChange}
disabled={props.disabled}
helpText={CONNECTION_SECURITY_HELP_TEXT_WEBSERVER}
/>
);
}
ConnectionSecurityDropdownSettingWebserver.defaultProps = {
};
ConnectionSecurityDropdownSettingWebserver.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
};

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

@@ -1,87 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ConnectionSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.AllowCorsFrom = this.state.allowCorsFrom;
config.ServiceSettings.EnableInsecureOutgoingConnections = this.state.enableInsecureOutgoingConnections;
return config;
}
getStateFromConfig(config) {
return {
allowCorsFrom: config.ServiceSettings.AllowCorsFrom,
enableInsecureOutgoingConnections: config.ServiceSettings.EnableInsecureOutgoingConnections
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.security.connection'
defaultMessage='Connections'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<TextSetting
id='allowCorsFrom'
label={
<FormattedMessage
id='admin.service.corsTitle'
defaultMessage='Enable cross-origin requests from:'
/>
}
placeholder={Utils.localizeMessage('admin.service.corsEx', 'http://example.com')}
helpText={
<FormattedMessage
id='admin.service.corsDescription'
defaultMessage='Enable HTTP Cross origin request from a specific domain. Use "*" if you want to allow CORS from any domain or leave it blank to disable it.'
/>
}
value={this.state.allowCorsFrom}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableInsecureOutgoingConnections'
label={
<FormattedMessage
id='admin.service.insecureTlsTitle'
defaultMessage='Enable Insecure Outgoing Connections: '
/>
}
helpText={
<FormattedMessage
id='admin.service.insecureTlsDesc'
defaultMessage='When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.'
/>
}
value={this.state.enableInsecureOutgoingConnections}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,156 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import BrandImageSetting from './brand_image_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import Constants from 'utils/constants.jsx';
export default class CustomBrandSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.TeamSettings.SiteName = this.state.siteName;
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
config.TeamSettings.customDescriptionText = this.state.customDescriptionText;
config.TeamSettings.EnableCustomBrand = this.state.enableCustomBrand;
config.TeamSettings.CustomBrandText = this.state.customBrandText;
}
return config;
}
getStateFromConfig(config) {
return {
siteName: config.TeamSettings.SiteName,
enableCustomBrand: config.TeamSettings.EnableCustomBrand,
customBrandText: config.TeamSettings.CustomBrandText,
customDescriptionText: config.TeamSettings.CustomDescriptionText
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.customization.customBrand'
defaultMessage='Custom Branding'
/>
);
}
renderSettings() {
const enterpriseSettings = [];
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
enterpriseSettings.push(
<TextSetting
key='customDescriptionText'
id='customDescriptionText'
label={
<FormattedMessage
id='admin.team.brandDescriptionTitle'
defaultMessage='Site Description: '
/>
}
helpText={
<FormattedMessage
id='admin.team.brandDescriptionHelp'
defaultMessage='Description of service shown in login screens and UI. When not specified, "All team communication in one place, searchable and accessible anywhere" is displayed.'
/>
}
value={this.state.customDescriptionText}
placeholder={Utils.localizeMessage('web.root.signup_info', 'All team communication in one place, searchable and accessible anywhere')}
onChange={this.handleChange}
/>
);
enterpriseSettings.push(
<BooleanSetting
key='enableCustomBrand'
id='enableCustomBrand'
label={
<FormattedMessage
id='admin.team.brandTitle'
defaultMessage='Enable Custom Branding: '
/>
}
helpText={
<FormattedMessage
id='admin.team.brandDesc'
defaultMessage='Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.'
/>
}
value={this.state.enableCustomBrand}
onChange={this.handleChange}
/>
);
enterpriseSettings.push(
<BrandImageSetting
key='customBrandImage'
disabled={!this.state.enableCustomBrand}
/>
);
enterpriseSettings.push(
<TextSetting
key='customBrandText'
id='customBrandText'
type='textarea'
label={
<FormattedMessage
id='admin.team.brandTextTitle'
defaultMessage='Custom Brand Text:'
/>
}
helpText={
<FormattedMessage
id='admin.team.brandTextDescription'
defaultMessage='Text that will appear below your custom brand image on your login screen. Supports Markdown-formatted text. Maximum 500 characters allowed.'
/>
}
value={this.state.customBrandText}
onChange={this.handleChange}
disabled={!this.state.enableCustomBrand}
/>
);
}
return (
<SettingsGroup>
<TextSetting
id='siteName'
label={
<FormattedMessage
id='admin.team.siteNameTitle'
defaultMessage='Site Name:'
/>
}
maxLength={Constants.MAX_SITENAME_LENGTH}
placeholder={Utils.localizeMessage('admin.team.siteNameExample', 'Ex "Mattermost"')}
helpText={
<FormattedMessage
id='admin.team.siteNameDescription'
defaultMessage='Name of service shown in login screens and UI.'
/>
}
value={this.state.siteName}
onChange={this.handleChange}
/>
{enterpriseSettings}
</SettingsGroup>
);
}
}

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

@@ -1,121 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class CustomEmojiSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.EnableCustomEmoji = this.state.enableCustomEmoji;
config.ServiceSettings.EnableEmojiPicker = this.state.enableEmojiPicker;
if (global.window.mm_license.IsLicensed === 'true') {
config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation;
}
return config;
}
getStateFromConfig(config) {
return {
enableCustomEmoji: config.ServiceSettings.EnableCustomEmoji,
enableEmojiPicker: config.ServiceSettings.EnableEmojiPicker,
restrictCustomEmojiCreation: config.ServiceSettings.RestrictCustomEmojiCreation
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.customization.emoji'
defaultMessage='Emoji'
/>
);
}
renderSettings() {
let restrictSetting = null;
if (global.window.mm_license.IsLicensed === 'true') {
restrictSetting = (
<DropdownSetting
id='restrictCustomEmojiCreation'
values={[
{value: 'all', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAll', 'Allow everyone to create custom emoji')},
{value: 'admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAdmin', 'Allow System and Team Admins to create custom emoji')},
{value: 'system_admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationSystemAdmin', 'Only allow System Admins to create custom emoji')}
]}
label={
<FormattedMessage
id='admin.customization.restrictCustomEmojiCreationTitle'
defaultMessage='Restrict Custom Emoji Creation:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.restrictCustomEmojiCreationDesc'
defaultMessage='Restrict the creation of custom emoji to certain users.'
/>
}
value={this.state.restrictCustomEmojiCreation}
onChange={this.handleChange}
disabled={!this.state.enableCustomEmoji}
/>
);
}
return (
<SettingsGroup>
<BooleanSetting
id='enableEmojiPicker'
label={
<FormattedMessage
id='admin.customization.enableEmojiPickerTitle'
defaultMessage='Enable Emoji Picker:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.enableEmojiPickerDesc'
defaultMessage='The emoji picker allows users to select emoji to add as reactions or use in messages. Enabling the emoji picker with a large number of custom emoji may slow down performance.'
/>
}
value={this.state.enableEmojiPicker}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableCustomEmoji'
label={
<FormattedMessage
id='admin.customization.enableCustomEmojiTitle'
defaultMessage='Enable Custom Emoji:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.enableCustomEmojiDesc'
defaultMessage='Enable users to create custom emoji for use in messages. When enabled, Custom Emoji settings can be accessed by switching to a team and clicking the three dots above the channel sidebar, and selecting "Custom Emoji".'
/>
}
value={this.state.enableCustomEmoji}
onChange={this.handleChange}
/>
{restrictSetting}
</SettingsGroup>
);
}
}

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

@@ -1,197 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class WebhookSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.EnableIncomingWebhooks = this.state.enableIncomingWebhooks;
config.ServiceSettings.EnableOutgoingWebhooks = this.state.enableOutgoingWebhooks;
config.ServiceSettings.EnableCommands = this.state.enableCommands;
config.ServiceSettings.EnableOnlyAdminIntegrations = this.state.enableOnlyAdminIntegrations;
config.ServiceSettings.EnablePostUsernameOverride = this.state.enablePostUsernameOverride;
config.ServiceSettings.EnablePostIconOverride = this.state.enablePostIconOverride;
config.ServiceSettings.EnableOAuthServiceProvider = this.state.enableOAuthServiceProvider;
config.ServiceSettings.EnableUserAccessTokens = this.state.enableUserAccessTokens;
return config;
}
getStateFromConfig(config) {
return {
enableIncomingWebhooks: config.ServiceSettings.EnableIncomingWebhooks,
enableOutgoingWebhooks: config.ServiceSettings.EnableOutgoingWebhooks,
enableCommands: config.ServiceSettings.EnableCommands,
enableOnlyAdminIntegrations: config.ServiceSettings.EnableOnlyAdminIntegrations,
enablePostUsernameOverride: config.ServiceSettings.EnablePostUsernameOverride,
enablePostIconOverride: config.ServiceSettings.EnablePostIconOverride,
enableOAuthServiceProvider: config.ServiceSettings.EnableOAuthServiceProvider,
enableUserAccessTokens: config.ServiceSettings.EnableUserAccessTokens
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.integrations.custom'
defaultMessage='Custom Integrations'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableIncomingWebhooks'
label={
<FormattedMessage
id='admin.service.webhooksTitle'
defaultMessage='Enable Incoming Webhooks: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.webhooksDescription'
defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableIncomingWebhooks}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOutgoingWebhooks'
label={
<FormattedMessage
id='admin.service.outWebhooksTitle'
defaultMessage='Enable Outgoing Webhooks: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.outWebhooksDesc'
defaultMessage='When true, outgoing webhooks will be allowed. See <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableOutgoingWebhooks}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableCommands'
label={
<FormattedMessage
id='admin.service.cmdsTitle'
defaultMessage='Enable Custom Slash Commands: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.cmdsDesc'
defaultMessage='When true, custom slash commands will be allowed. See <a href="http://docs.mattermost.com/developer/slash-commands.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableCommands}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOAuthServiceProvider'
label={
<FormattedMessage
id='admin.oauth.providerTitle'
defaultMessage='Enable OAuth 2.0 Service Provider: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.oauth.providerDescription'
defaultMessage='When true, Mattermost can act as an OAuth 2.0 service provider allowing Mattermost to authorize API requests from external applications. See <a href="https://docs.mattermost.com/developer/oauth-2-0-applications.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableOAuthServiceProvider}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOnlyAdminIntegrations'
label={
<FormattedMessage
id='admin.service.integrationAdmin'
defaultMessage='Restrict managing integrations to Admins:'
/>
}
helpText={
<FormattedMessage
id='admin.service.integrationAdminDesc'
defaultMessage='When true, webhooks and slash commands can only be created, edited and viewed by Team and System Admins, and OAuth 2.0 applications by System Admins. Integrations are available to all users after they have been created by the Admin.'
/>
}
value={this.state.enableOnlyAdminIntegrations}
onChange={this.handleChange}
/>
<BooleanSetting
id='enablePostUsernameOverride'
label={
<FormattedMessage
id='admin.service.overrideTitle'
defaultMessage='Enable integrations to override usernames:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.overrideDescription'
defaultMessage='When true, webhooks, slash commands and other integrations, such as <a href="https://docs.mattermost.com/integrations/zapier.html" target="_blank">Zapier</a>, will be allowed to change the username they are posting as. Note: Combined with allowing integrations to override profile picture icons, users may be able to perform phishing attacks by attempting to impersonate other users.'
/>
}
value={this.state.enablePostUsernameOverride}
onChange={this.handleChange}
/>
<BooleanSetting
id='enablePostIconOverride'
label={
<FormattedMessage
id='admin.service.iconTitle'
defaultMessage='Enable integrations to override profile picture icons:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.iconDescription'
defaultMessage='When true, webhooks, slash commands and other integrations, such as <a href="https://docs.mattermost.com/integrations/zapier.html" target="_blank">Zapier</a>, will be allowed to change the profile picture they post with. Note: Combined with allowing integrations to override usernames, users may be able to perform phishing attacks by attempting to impersonate other users.'
/>
}
value={this.state.enablePostIconOverride}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableUserAccessTokens'
label={
<FormattedMessage
id='admin.service.userAccessTokensTitle'
defaultMessage='Enable User Access Tokens: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.userAccessTokensDescription'
defaultMessage='When true, users can create <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a> for integrations in <strong>Account Settings > Security</strong>. They can be used to authenticate against the API and give full access to the account.<br/><br/>To manage who can create personal access tokens or to search users by token ID, go to the <strong>System Console > Users</strong> page.'
/>
}
value={this.state.enableUserAccessTokens}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,238 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {recycleDatabaseConnection} from 'actions/admin_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import RequestButton from './request_button/request_button.jsx';
export default class DatabaseSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
// driverName and dataSource are read-only from the UI
config.SqlSettings.MaxIdleConns = this.parseIntNonZero(this.state.maxIdleConns);
config.SqlSettings.MaxOpenConns = this.parseIntNonZero(this.state.maxOpenConns);
config.SqlSettings.AtRestEncryptKey = this.state.atRestEncryptKey;
config.SqlSettings.Trace = this.state.trace;
config.SqlSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
return config;
}
getStateFromConfig(config) {
return {
driverName: config.SqlSettings.DriverName,
dataSource: config.SqlSettings.DataSource,
maxIdleConns: config.SqlSettings.MaxIdleConns,
maxOpenConns: config.SqlSettings.MaxOpenConns,
atRestEncryptKey: config.SqlSettings.AtRestEncryptKey,
trace: config.SqlSettings.Trace,
queryTimeout: config.SqlSettings.QueryTimeout
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.database.title'
defaultMessage='Database Settings'
/>
);
}
renderSettings() {
const dataSource = '**********' + this.state.dataSource.substring(this.state.dataSource.indexOf('@'));
let recycleDbButton = <div/>;
if (global.window.mm_license.IsLicensed === 'true') {
recycleDbButton = (
<RequestButton
requestAction={recycleDatabaseConnection}
helpText={
<FormattedMessage
id='admin.recycle.recycleDescription'
defaultMessage='Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {reloadConfiguration} feature to load the new settings while the server is running. The administrator should then use {featureName} feature to recycle the database connections based on the new settings.'
values={{
featureName: (
<b>
<FormattedMessage
id='admin.recycle.recycleDescription.featureName'
defaultMessage='Recycle Database Connections'
/>
</b>
),
reloadConfiguration: (
<a href='../general/configuration'>
<b>
<FormattedMessage
id='admin.recycle.recycleDescription.reloadConfiguration'
defaultMessage='Configuration > Reload Configuration from Disk'
/>
</b>
</a>
)
}}
/>
}
buttonText={
<FormattedMessage
id='admin.recycle.button'
defaultMessage='Recycle Database Connections'
/>
}
showSuccessMessage={false}
errorMessage={{
id: 'admin.recycle.reloadFail',
defaultMessage: 'Recycling unsuccessful: {error}'
}}
includeDetailedError={true}
/>
);
}
return (
<SettingsGroup>
<div className='banner'>
<FormattedMessage
id='admin.sql.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DriverName'
>
<FormattedMessage
id='admin.sql.driverName'
defaultMessage='Driver Name:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{this.state.driverName}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DataSource'
>
<FormattedMessage
id='admin.sql.dataSource'
defaultMessage='Data Source:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSource}</p>
</div>
</div>
<TextSetting
id='maxIdleConns'
label={
<FormattedMessage
id='admin.sql.maxConnectionsTitle'
defaultMessage='Maximum Idle Connections:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxConnectionsExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.sql.maxConnectionsDescription'
defaultMessage='Maximum number of idle connections held open to the database.'
/>
}
value={this.state.maxIdleConns}
onChange={this.handleChange}
/>
<TextSetting
id='maxOpenConns'
label={
<FormattedMessage
id='admin.sql.maxOpenTitle'
defaultMessage='Maximum Open Connections:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.sql.maxOpenDescription'
defaultMessage='Maximum number of open connections held open to the database.'
/>
}
value={this.state.maxOpenConns}
onChange={this.handleChange}
/>
<TextSetting
id='queryTimeout'
label={
<FormattedMessage
id='admin.sql.queryTimeoutTitle'
defaultMessage='Query Timeout:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.queryTimeoutExample', 'Ex "30"')}
helpText={
<FormattedMessage
id='admin.sql.queryTimeoutDescription'
defaultMessage='The number of seconds to wait for a response from the database after opening a connection and sending the query. Errors that you see in the UI or in the logs as a result of a query timeout can vary depending on the type of query.'
/>
}
value={this.state.queryTimeout}
onChange={this.handleChange}
/>
<GeneratedSetting
id='atRestEncryptKey'
label={
<FormattedMessage
id='admin.sql.keyTitle'
defaultMessage='At Rest Encrypt Key:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.keyExample', 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"')}
helpText={
<FormattedMessage
id='admin.sql.keyDescription'
defaultMessage='32-character salt available to encrypt and decrypt sensitive fields in database.'
/>
}
value={this.state.atRestEncryptKey}
onChange={this.handleChange}
/>
<BooleanSetting
id='trace'
label={
<FormattedMessage
id='admin.sql.traceTitle'
defaultMessage='Trace: '
/>
}
helpText={
<FormattedMessage
id='admin.sql.traceDescription'
defaultMessage='(Development Mode) When true, executing SQL statements are written to the log.'
/>
}
value={this.state.trace}
onChange={this.handleChange}
/>
{recycleDbButton}
</SettingsGroup>
);
}
}

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

@@ -1,106 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class DeveloperSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.EnableTesting = this.state.enableTesting;
config.ServiceSettings.EnableDeveloper = this.state.enableDeveloper;
config.ServiceSettings.AllowedUntrustedInternalConnections = this.state.allowedUntrustedInternalConnections;
return config;
}
getStateFromConfig(config) {
return {
enableTesting: config.ServiceSettings.EnableTesting,
enableDeveloper: config.ServiceSettings.EnableDeveloper,
allowedUntrustedInternalConnections: config.ServiceSettings.AllowedUntrustedInternalConnections
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.developer.title'
defaultMessage='Developer Settings'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableTesting'
label={
<FormattedMessage
id='admin.service.testingTitle'
defaultMessage='Enable Testing Commands: '
/>
}
helpText={
<FormattedMessage
id='admin.service.testingDescription'
defaultMessage='When true, /test slash command is enabled to load test accounts, data and text formatting. Changing this requires a server restart before taking effect.'
/>
}
value={this.state.enableTesting}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableDeveloper'
label={
<FormattedMessage
id='admin.service.developerTitle'
defaultMessage='Enable Developer Mode: '
/>
}
helpText={
<FormattedMessage
id='admin.service.developerDesc'
defaultMessage='When true, JavaScript errors are shown in a purple bar at the top of the user interface. Not recommended for use in production. '
/>
}
value={this.state.enableDeveloper}
onChange={this.handleChange}
/>
<TextSetting
id='allowedUntrustedInternalConnections'
label={
<FormattedMessage
id='admin.service.internalConnectionsTitle'
defaultMessage='Allow untrusted internal connections to: '
/>
}
placeholder={Utils.localizeMessage('admin.service.internalConnectionsEx', 'webhooks.internal.example.com 127.0.0.1 10.0.16.0/28')}
helpText={
<FormattedHTMLMessage
id='admin.service.internalConnectionsDesc'
defaultMessage='In testing environments, such as when developing integrations locally on a development machine, use this setting to specify domains, IP addresses, or CIDR notations to allow internal connections. <b>Not recommended for use in production</b>, since this can allow a user to extract confidential data from your server or internal network.<br /><br />By default, user-supplied URLs such as those used for Open Graph metadata, webhooks, or slash commands will not be allowed to connect to reserved IP addresses including loopback or link-local addresses used for internal networks. Push notification, OAuth 2.0 and WebRTC server URLs are trusted and not affected by this setting.'
/>
}
value={this.state.allowedUntrustedInternalConnections}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,66 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Setting from './setting.jsx';
export default class DropdownSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value);
}
render() {
const options = [];
for (const {value, text} of this.props.values) {
options.push(
<option
value={value}
key={value}
>
{text}
</option>
);
}
return (
<Setting
label={this.props.label}
inputId={this.props.id}
helpText={this.props.helpText}
>
<select
className='form-control'
id={this.props.id}
value={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
>
{options}
</select>
</Setting>
);
}
}
DropdownSetting.defaultProps = {
isDisabled: false
};
DropdownSetting.propTypes = {
id: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
helpText: PropTypes.node
};

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

@@ -1,333 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {elasticsearchTest, elasticsearchPurgeIndexes} from 'actions/admin_actions.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import RequestButton from './request_button/request_button.jsx';
import ElasticsearchStatus from './elasticsearch_status';
export default class ElasticsearchSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.doTestConfig = this.doTestConfig.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSaved = this.handleSaved.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ElasticsearchSettings.ConnectionUrl = this.state.connectionUrl;
config.ElasticsearchSettings.Username = this.state.username;
config.ElasticsearchSettings.Password = this.state.password;
config.ElasticsearchSettings.Sniff = this.state.sniff;
config.ElasticsearchSettings.EnableIndexing = this.state.enableIndexing;
config.ElasticsearchSettings.EnableSearching = this.state.enableSearching;
return config;
}
getStateFromConfig(config) {
return {
connectionUrl: config.ElasticsearchSettings.ConnectionUrl,
username: config.ElasticsearchSettings.Username,
password: config.ElasticsearchSettings.Password,
sniff: config.ElasticsearchSettings.Sniff,
enableIndexing: config.ElasticsearchSettings.EnableIndexing,
enableSearching: config.ElasticsearchSettings.EnableSearching,
configTested: true,
canSave: true,
canPurgeAndIndex: config.ElasticsearchSettings.EnableIndexing
};
}
handleChange(id, value) {
if (id === 'enableIndexing') {
if (value === false) {
this.setState({
enableSearching: false
});
} else {
this.setState({
canSave: false,
configTested: false
});
}
}
if (id === 'connectionUrl' || id === 'username' || id === 'password' || id === 'sniff') {
this.setState({
configTested: false,
canSave: false
});
}
if (id !== 'enableSearching') {
this.setState({
canPurgeAndIndex: false
});
}
super.handleChange(id, value);
}
handleSaved() {
this.setState({
canPurgeAndIndex: this.state.enableIndexing
});
}
canSave() {
return this.state.canSave;
}
doTestConfig(success, error) {
const config = JSON.parse(JSON.stringify(this.props.config));
this.getConfigFromState(config);
elasticsearchTest(
config,
() => {
this.setState({
configTested: true,
canSave: true
});
success();
this.doSubmit();
},
(err) => {
this.setState({
configTested: false,
canSave: false
});
error(err);
}
);
}
renderTitle() {
return (
<FormattedMessage
id='admin.elasticsearch.title'
defaultMessage='Elasticsearch (Beta)'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<FormattedMessage
id='admin.elasticsearch.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</div>
</div>
<BooleanSetting
id='enableIndexing'
label={
<FormattedMessage
id='admin.elasticsearch.enableIndexingTitle'
defaultMessage='Enable Elasticsearch Indexing:'
/>
}
helpText={
<FormattedMessage
id='admin.elasticsearch.enableIndexingDescription'
defaultMessage='When true, indexing of new posts occurs automatically. Search queries will use database search until "Enable Elasticsearch for search queries" is enabled. {documentationLink}'
values={{
documentationLink: (
<a
href='https://about.mattermost.com/default-elasticsearch-documentation/'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='admin.elasticsearch.enableIndexingDescription.documentationLinkText'
defaultMessage='Learn more about Elasticsearch in our documentation.'
/>
</a>
)
}}
/>
}
value={this.state.enableIndexing}
onChange={this.handleChange}
/>
<TextSetting
id='connectionUrl'
label={
<FormattedMessage
id='admin.elasticsearch.connectionUrlTitle'
defaultMessage='Server Connection Address:'
/>
}
placeholder={Utils.localizeMessage('admin.elasticsearch.connectionUrlExample', 'E.g.: "https://elasticsearch.example.org:9200"')}
helpText={
<FormattedMessage
id='admin.elasticsearch.connectionUrlDescription'
defaultMessage='The address of the Elasticsearch server. {documentationLink}'
values={{
documentationLink: (
<a
href='https://about.mattermost.com/default-elasticsearch-server-setup/'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='admin.elasticsearch.connectionUrlExample.documentationLinkText'
defaultMessage='Please see documentation with server setup instructions.'
/>
</a>
)
}}
/>
}
value={this.state.connectionUrl}
disabled={!this.state.enableIndexing}
onChange={this.handleChange}
/>
<TextSetting
id='username'
label={
<FormattedMessage
id='admin.elasticsearch.usernameTitle'
defaultMessage='Server Username:'
/>
}
placeholder={Utils.localizeMessage('admin.elasticsearch.usernameExample', 'E.g.: "elastic"')}
helpText={
<FormattedMessage
id='admin.elasticsearch.usernameDescription'
defaultMessage='(Optional) The username to authenticate to the Elasticsearch server.'
/>
}
value={this.state.username}
disabled={!this.state.enableIndexing}
onChange={this.handleChange}
/>
<TextSetting
id='password'
label={
<FormattedMessage
id='admin.elasticsearch.passwordTitle'
defaultMessage='Server Password:'
/>
}
placeholder={Utils.localizeMessage('admin.elasticsearch.password', 'E.g.: "yourpassword"')}
helpText={
<FormattedMessage
id='admin.elasticsearch.passwordDescription'
defaultMessage='(Optional) The password to authenticate to the Elasticsearch server.'
/>
}
value={this.state.password}
disabled={!this.state.enableIndexing}
onChange={this.handleChange}
/>
<BooleanSetting
id='sniff'
label={
<FormattedMessage
id='admin.elasticsearch.sniffTitle'
defaultMessage='Enable Cluster Sniffing:'
/>
}
helpText={
<FormattedMessage
id='admin.elasticsearch.sniffDescription'
defaultMessage='When true, sniffing finds and connects to all data nodes in your cluster automatically.'
/>
}
value={this.state.sniff}
disabled={!this.state.enableIndexing}
onChange={this.handleChange}
/>
<RequestButton
requestAction={this.doTestConfig}
helpText={
<FormattedMessage
id='admin.elasticsearch.testHelpText'
defaultMessage='Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection only saves the configuration if the test is successful. See log file for more detailed error messages.'
/>
}
buttonText={
<FormattedMessage
id='admin.elasticsearch.elasticsearch_test_button'
defaultMessage='Test Connection'
/>
}
successMessage={{
id: 'admin.elasticsearch.testConfigSuccess',
defaultMessage: 'Test successful. Configuration saved.'
}}
disabled={!this.state.enableIndexing}
/>
<ElasticsearchStatus
isConfigured={this.state.canPurgeAndIndex}
/>
<RequestButton
requestAction={elasticsearchPurgeIndexes}
helpText={
<FormattedMessage
id='admin.elasticsearch.purgeIndexesHelpText'
defaultMessage='Purging will entirely remove the index on the Elasticsearch server. Search results may be incomplete until a bulk index of the existing post database is rebuilt.'
/>
}
buttonText={
<FormattedMessage
id='admin.elasticsearch.purgeIndexesButton'
defaultMessage='Purge Index'
/>
}
successMessage={{
id: 'admin.elasticsearch.purgeIndexesButton.success',
defaultMessage: 'Indexes purged successfully.'
}}
errorMessage={{
id: 'admin.elasticsearch.purgeIndexesButton.error',
defaultMessage: 'Failed to purge indexes: {error}'
}}
disabled={!this.state.canPurgeAndIndex}
label={(
<FormattedMessage
id='admin.elasticsearch.purgeIndexesButton.label'
defaultMessage='Purge Indexes:'
/>
)}
/>
<BooleanSetting
id='enableSearching'
label={
<FormattedMessage
id='admin.elasticsearch.enableSearchingTitle'
defaultMessage='Enable Elasticsearch for search queries:'
/>
}
helpText={
<FormattedMessage
id='admin.elasticsearch.enableSearchingDescription'
defaultMessage='Requires a successful connection to the Elasticsearch server. When true, Elasticsearch will be used for all search queries using the latest index. Search results may be incomplete until a bulk index of the existing post database is finished. When false, database search is used.'
/>
}
value={this.state.enableSearching}
disabled={!this.state.enableIndexing || !this.state.configTested}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,28 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getJobsByType} from 'mattermost-redux/actions/jobs';
import {JobTypes} from 'utils/constants.jsx';
import * as Selectors from 'mattermost-redux/selectors/entities/jobs';
import Status from './status.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
jobs: Selectors.makeGetJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING)(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getJobsByType
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Status);

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

@@ -1,361 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {createJob, cancelJob} from 'actions/job_actions.jsx';
import {JobTypes, JobStatuses} from 'utils/constants.jsx';
import RequestButton from '../request_button/request_button.jsx';
export default class Status extends React.PureComponent {
static propTypes = {
/**
* Array of jobs
*/
jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
/**
* Whether Elasticsearch is properly configured.
*/
isConfigured: PropTypes.bool.isRequired,
actions: PropTypes.shape({
/**
* Function to fetch jobs
*/
getJobsByType: PropTypes.func.isRequired
}).isRequired
};
constructor(props) {
super(props);
this.interval = null;
this.state = {
loading: true,
cancelInProgress: false
};
}
componentWillMount() {
// reload the cluster status every 15 seconds
this.interval = setInterval(this.reload, 15000);
}
componentDidMount() {
this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
() => this.setState({loading: false})
);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
reload = () => {
this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
() => {
this.setState({
loading: false,
cancelInProgress: false
});
}
);
};
createIndexJob = (success, error) => {
const job = {
type: JobTypes.ELASTICSEARCH_POST_INDEXING
};
createJob(
job,
() => {
this.reload();
success();
},
error
);
};
cancelIndexJob = (e) => {
e.preventDefault();
const chosenJob = this.getChosenJob();
if (!chosenJob) {
return;
}
this.setState({
cancelInProgress: true
});
cancelJob(
chosenJob.id,
() => {
this.reload();
},
() => {
this.reload();
}
);
};
getChosenJob = () => {
let chosenJob = null;
if (this.props.jobs.length > 0) {
for (let i = 0; i < this.props.jobs.length; i++) {
const job = this.props.jobs[i];
if (job.status === JobStatuses.CANCEL_REQUESTED || job.status === JobStatuses.IN_PROGRESS) {
chosenJob = job;
} else {
break;
}
}
if (!chosenJob) {
for (let i = 0; i < this.props.jobs.length; i++) {
const job = this.props.jobs[i];
if (job.status !== JobStatuses.PENDING && chosenJob) {
continue;
} else {
chosenJob = job;
break;
}
}
}
}
return chosenJob;
};
render() {
const chosenJob = this.getChosenJob();
let indexButtonDisabled = !this.props.isConfigured;
let buttonText = (
<FormattedMessage
id='admin.elasticsearch.indexButton.ready'
defaultMessage='Build Index'
/>
);
let cancelButton = null;
let indexButtonHelp = (
<FormattedMessage
id='admin.elasticsearch.indexHelpText.buildIndex'
defaultMessage='All posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.'
/>
);
if (this.state.loading) {
indexButtonDisabled = true;
} else if (chosenJob) {
if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
indexButtonDisabled = true;
buttonText = (
<span>
<span className='fa fa-refresh icon--rotate'/>
<FormattedMessage
id='admin.elasticsearch.indexButton.inProgress'
defaultMessage='Indexing in progress'
/>
</span>
);
}
if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
indexButtonHelp = (
<FormattedMessage
id='admin.elasticsearch.indexHelpText.cancelIndexing'
defaultMessage='Cancelling stops the indexing job and removes it from the queue. Posts that have already been indexed will not be deleted.'
/>
);
}
if (!this.state.cancelInProgress && (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS)) {
cancelButton = (
<a
href='#'
className='btn btn-link'
onClick={this.cancelIndexJob}
>
<FormattedMessage
id='admin.elasticsearchStatus.cancelButton'
defaultMessage='Cancel'
/>
</a>
);
}
}
const indexButton = (
<RequestButton
requestAction={this.createIndexJob}
helpText={indexButtonHelp}
buttonText={buttonText}
disabled={indexButtonDisabled}
showSuccessMessage={false}
errorMessage={{
id: 'admin.elasticsearch.bulkIndexButton.error',
defaultMessage: 'Failed to schedule Bulk Index Job: {error}'
}}
alternativeActionElement={cancelButton}
label={(
<FormattedMessage
id='admin.elasticsearchStatus.bulkIndexLabel'
defaultMessage='Bulk Indexing:'
/>
)}
/>
);
let status = null;
let statusHelp = null;
let statusClass = null;
if (!this.props.isConfigured) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusIndexingDisabled'
defaultMessage='Indexing disabled.'
/>
);
} else if (this.state.loading) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusLoading'
defaultMessage='Loading...'
/>
);
statusClass = 'status-icon-unknown';
} else if (chosenJob) {
if (chosenJob.status === JobStatuses.PENDING) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusPending'
defaultMessage='Job pending.'
/>
);
statusHelp = (
<FormattedMessage
id='admin.elasticsearchStatus.statusPending.help'
defaultMessage='Elasticsearch index job is queued on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
/>
);
statusClass = 'status-icon-warning';
} else if (chosenJob.status === JobStatuses.IN_PROGRESS) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusInProgress'
defaultMessage='Job in progress. {percent}% complete.'
values={{
percent: chosenJob.progress
}}
/>
);
statusHelp = (
<FormattedMessage
id='admin.elasticsearchStatus.statusInProgress.help'
defaultMessage='Indexing is in progress on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
/>
);
statusClass = 'status-icon-warning';
} else if (chosenJob.status === JobStatuses.SUCCESS) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusSuccess'
defaultMessage='Indexing complete.'
/>
);
statusHelp = (
<FormattedMessage
id='admin.elasticsearchStatus.statusSuccess.help'
defaultMessage='Indexing is complete and new posts are being automatically indexed.'
/>
);
statusClass = 'status-icon-success';
} else if (chosenJob.status === JobStatuses.ERROR) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusError'
defaultMessage='Indexing error.'
/>
);
statusHelp = (
<FormattedMessage
id='admin.elasticsearchStatus.statusError.help'
defaultMessage='Mattermost encountered an error building the Elasticsearch index: {error}'
values={{
error: chosenJob.data ? (chosenJob.data.error || '') : ''
}}
/>
);
statusClass = 'status-icon-error';
} else if (chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusRequestCancel'
defaultMessage='Canceling Job...'
/>
);
statusClass = 'status-icon-warning';
} else if (chosenJob.status === JobStatuses.CANCELED) {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusCancelled'
defaultMessage='Indexing job cancelled.'
/>
);
statusClass = 'status-icon-error';
}
} else {
status = (
<FormattedMessage
id='admin.elasticsearchStatus.statusNoJobs'
defaultMessage='No indexing jobs queued.'
/>
);
statusClass = 'status-icon-unknown';
}
if (statusHelp !== null) {
statusHelp = (
<div className='col-sm-offset-4 col-sm-8'>
<div className='help-text'>
{statusHelp}
</div>
</div>
);
}
statusClass = 'fa fa-circle margin--right ' + statusClass;
return (
<div>
{indexButton}
<div className='form-group'>
<div className='col-sm-offset-4 col-sm-8'>
<div className='help-text no-margin'>
<FormattedMessage
id='admin.elasticsearchStatus.status'
defaultMessage='Status: '
/>
<i
className={statusClass}
/>
{status}
</div>
</div>
{statusHelp}
</div>
</div>
);
}
}

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

@@ -1,102 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class EmailAuthenticationSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.EmailSettings.EnableSignUpWithEmail = this.state.enableSignUpWithEmail;
config.EmailSettings.EnableSignInWithEmail = this.state.enableSignInWithEmail;
config.EmailSettings.EnableSignInWithUsername = this.state.enableSignInWithUsername;
return config;
}
getStateFromConfig(config) {
return {
enableSignUpWithEmail: config.EmailSettings.EnableSignUpWithEmail,
enableSignInWithEmail: config.EmailSettings.EnableSignInWithEmail,
enableSignInWithUsername: config.EmailSettings.EnableSignInWithUsername
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.email'
defaultMessage='Email Authentication'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableSignUpWithEmail'
label={
<FormattedMessage
id='admin.email.allowSignupTitle'
defaultMessage='Enable account creation with email: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowSignupDescription'
defaultMessage='When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML or GitLab.'
/>
}
value={this.state.enableSignUpWithEmail}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableSignInWithEmail'
label={
<FormattedMessage
id='admin.email.allowEmailSignInTitle'
defaultMessage='Enable sign-in with email: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowEmailSignInDescription'
defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
/>
}
value={this.state.enableSignInWithEmail}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableSignInWithUsername'
label={
<FormattedMessage
id='admin.email.allowUsernameSignInTitle'
defaultMessage='Enable sign-in with username: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowUsernameSignInDescription'
defaultMessage='When true, users with email login can sign in using their username and password. This setting does not affect AD/LDAP login.'
/>
}
value={this.state.enableSignInWithUsername}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,130 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import {testEmail} from 'actions/admin_actions.jsx';
export default class EmailConnectionTestButton extends React.Component {
static get propTypes() {
return {
config: PropTypes.object.isRequired,
getConfigFromState: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
};
}
constructor(props) {
super(props);
this.handleTestConnection = this.handleTestConnection.bind(this);
this.state = {
testing: false,
success: false,
fail: null
};
}
handleTestConnection(e) {
e.preventDefault();
this.setState({
testing: true,
success: false,
fail: null
});
const config = JSON.parse(JSON.stringify(this.props.config));
this.props.getConfigFromState(config);
testEmail(
config,
() => {
this.setState({
testing: false,
success: true
});
},
(err) => {
let fail = err.message;
if (err.detailed_error) {
fail += ' - ' + err.detailed_error;
}
this.setState({
testing: false,
fail
});
}
);
}
render() {
let testMessage = null;
if (this.state.success) {
testMessage = (
<div className='alert alert-success'>
<i className='fa fa-check'/>
<FormattedMessage
id='admin.email.emailSuccess'
defaultMessage='No errors were reported while sending an email. Please check your inbox to make sure.'
/>
</div>
);
} else if (this.state.fail) {
testMessage = (
<div className='alert alert-warning'>
<i className='fa fa-warning'/>
<FormattedMessage
id='admin.email.emailFail'
defaultMessage='Connection unsuccessful: {error}'
values={{
error: this.state.fail
}}
/>
</div>
);
}
let contents = null;
if (this.state.testing) {
contents = (
<span>
<span className='fa fa-refresh icon--rotate'/>
{Utils.localizeMessage('admin.email.testing', 'Testing...')}
</span>
);
} else {
contents = (
<FormattedMessage
id='admin.email.connectionSecurityTest'
defaultMessage='Test Connection'
/>
);
}
return (
<div className='form-group email-connection-test'>
<div className='col-sm-offset-4 col-sm-8'>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleTestConnection}
disabled={this.props.disabled}
>
{contents}
</button>
{testMessage}
</div>
</div>
</div>
);
}
}

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

@@ -1,395 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import ErrorStore from 'stores/error_store.jsx';
import {ErrorBarTypes} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {ConnectionSecurityDropdownSettingEmail} from './connection_security_dropdown_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import EmailConnectionTest from './email_connection_test.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
const EMAIL_NOTIFICATION_CONTENTS_FULL = 'full';
const EMAIL_NOTIFICATION_CONTENTS_GENERIC = 'generic';
export default class EmailSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSaved = this.handleSaved.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.EmailSettings.SendEmailNotifications = this.state.sendEmailNotifications;
config.EmailSettings.FeedbackName = this.state.feedbackName;
config.EmailSettings.FeedbackEmail = this.state.feedbackEmail;
config.EmailSettings.FeedbackOrganization = this.state.feedbackOrganization;
config.EmailSettings.EnableSMTPAuth = this.state.enableSMTPAuth;
config.EmailSettings.SMTPUsername = this.state.smtpUsername;
config.EmailSettings.SMTPPassword = this.state.smtpPassword;
config.EmailSettings.SMTPServer = this.state.smtpServer;
config.EmailSettings.SMTPPort = this.state.smtpPort;
config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity;
config.EmailSettings.EnableEmailBatching = this.state.enableEmailBatching;
config.ServiceSettings.EnableSecurityFixAlert = this.state.enableSecurityFixAlert;
config.EmailSettings.SkipServerCertificateVerification = this.state.skipServerCertificateVerification;
config.EmailSettings.EmailNotificationContentsType = this.state.emailNotificationContentsType;
return config;
}
handleSaved(newConfig) {
if (newConfig.EmailSettings.SendEmailNotifications) {
ErrorStore.clearError(ErrorBarTypes.PREVIEW_MODE);
}
}
getStateFromConfig(config) {
return {
sendEmailNotifications: config.EmailSettings.SendEmailNotifications,
feedbackName: config.EmailSettings.FeedbackName,
feedbackEmail: config.EmailSettings.FeedbackEmail,
feedbackOrganization: config.EmailSettings.FeedbackOrganization,
enableSMTPAuth: config.EmailSettings.EnableSMTPAuth,
smtpUsername: config.EmailSettings.SMTPUsername,
smtpPassword: config.EmailSettings.SMTPPassword,
smtpServer: config.EmailSettings.SMTPServer,
smtpPort: config.EmailSettings.SMTPPort,
connectionSecurity: config.EmailSettings.ConnectionSecurity,
enableEmailBatching: config.EmailSettings.EnableEmailBatching,
skipServerCertificateVerification: config.EmailSettings.SkipServerCertificateVerification,
enableSecurityFixAlert: config.ServiceSettings.EnableSecurityFixAlert,
emailNotificationContentsType: config.EmailSettings.EmailNotificationContentsType
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.notifications.email'
defaultMessage='Email'
/>
);
}
renderSettings() {
let enableEmailBatchingDisabledText = null;
if (this.props.config.ClusterSettings.Enable) {
enableEmailBatchingDisabledText = (
<span
key='admin.email.enableEmailBatching.clusterEnabled'
className='help-text'
>
<FormattedHTMLMessage
id='admin.email.enableEmailBatching.clusterEnabled'
defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
/>
</span>
);
} else if (!this.props.config.ServiceSettings.SiteURL) {
enableEmailBatchingDisabledText = (
<span
key='admin.email.enableEmailBatching.siteURL'
className='help-text'
>
<FormattedHTMLMessage
id='admin.email.enableEmailBatching.siteURL'
defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
/>
</span>
);
}
let emailNotificationContentsTypeDropdown = null;
let emailNotificationContentsHelpText = null;
if (window.mm_license.EmailNotificationContents === 'true') {
const emailNotificationContentsTypes = [];
emailNotificationContentsTypes.push({value: EMAIL_NOTIFICATION_CONTENTS_FULL, text: Utils.localizeMessage('admin.email.notification.contents.full', 'Send full message contents')});
emailNotificationContentsTypes.push({value: EMAIL_NOTIFICATION_CONTENTS_GENERIC, text: Utils.localizeMessage('admin.email.notification.contents.generic', 'Send generic description with only sender name')});
if (this.state.emailNotificationContentsType === EMAIL_NOTIFICATION_CONTENTS_FULL) {
emailNotificationContentsHelpText = (
<FormattedHTMLMessage
key='admin.email.notification.contents.full.description'
id='admin.email.notification.contents.full.description'
defaultMessage='Sender name and channel are included in email notifications.</br>Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'
/>
);
} else if (this.state.emailNotificationContentsType === EMAIL_NOTIFICATION_CONTENTS_GENERIC) {
emailNotificationContentsHelpText = (
<FormattedHTMLMessage
key='admin.email.notification.contents.generic.description'
id='admin.email.notification.contents.generic.description'
defaultMessage='Only the name of the person who sent the message, with no information about channel name or message contents are included in email notifications.</br>Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'
/>
);
}
emailNotificationContentsTypeDropdown = (
<DropdownSetting
id='emailNotificationContentsType'
values={emailNotificationContentsTypes}
label={
<FormattedMessage
id='admin.email.notification.contents.title'
defaultMessage='Email Notification Contents: '
/>
}
value={this.state.emailNotificationContentsType}
onChange={this.handleChange}
helpText={emailNotificationContentsHelpText}
/>
);
}
return (
<SettingsGroup>
<BooleanSetting
id='sendEmailNotifications'
label={
<FormattedMessage
id='admin.email.notificationsTitle'
defaultMessage='Enable Email Notifications: '
/>
}
helpText={
<FormattedHTMLMessage
id='admin.email.notificationsDescription'
defaultMessage='Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'
/>
}
value={this.state.sendEmailNotifications}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableEmailBatching'
label={
<FormattedMessage
id='admin.email.enableEmailBatchingTitle'
defaultMessage='Enable Email Batching: '
/>
}
helpText={[
<FormattedHTMLMessage
key='admin.email.enableEmailBatchingDesc'
id='admin.email.enableEmailBatchingDesc'
defaultMessage='When true, users will have email notifications for multiple direct messages and mentions combined into a single email. Batching will occur at a default interval of 15 minutes, configurable in Account Settings > Notifications.'
/>,
enableEmailBatchingDisabledText
]}
value={this.state.enableEmailBatching && !this.props.config.ClusterSettings.Enable && Boolean(this.props.config.ServiceSettings.SiteURL)}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications || this.props.config.ClusterSettings.Enable || !this.props.config.ServiceSettings.SiteURL}
/>
{emailNotificationContentsTypeDropdown}
<TextSetting
id='feedbackName'
label={
<FormattedMessage
id='admin.email.notificationDisplayTitle'
defaultMessage='Notification Display Name:'
/>
}
placeholder={Utils.localizeMessage('admin.email.notificationDisplayExample', 'Ex: "Mattermost Notification", "System", "No-Reply"')}
helpText={
<FormattedMessage
id='admin.email.notificationDisplayDescription'
defaultMessage='Display name on email account used when sending notification emails from Mattermost.'
/>
}
value={this.state.feedbackName}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<TextSetting
id='feedbackEmail'
label={
<FormattedMessage
id='admin.email.notificationEmailTitle'
defaultMessage='Notification From Address:'
/>
}
placeholder={Utils.localizeMessage('admin.email.notificationEmailExample', 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"')}
helpText={
<FormattedMessage
id='admin.email.notificationEmailDescription'
defaultMessage='Email address displayed on email account used when sending notification emails from Mattermost.'
/>
}
value={this.state.feedbackEmail}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<TextSetting
id='feedbackOrganization'
label={
<FormattedMessage
id='admin.email.notificationOrganization'
defaultMessage='Notification Footer Mailing Address:'
/>
}
placeholder={Utils.localizeMessage('admin.email.notificationOrganizationExample', 'Ex: "© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA"')}
helpText={
<FormattedMessage
id='admin.email.notificationOrganizationDescription'
defaultMessage='Organization name and address displayed on email notifications from Mattermost, such as "© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA". If the field is left empty, the organization name and address will not be displayed.'
/>
}
value={this.state.feedbackOrganization}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<TextSetting
id='smtpServer'
label={
<FormattedMessage
id='admin.email.smtpServerTitle'
defaultMessage='SMTP Server:'
/>
}
placeholder={Utils.localizeMessage('admin.email.smtpServerExample', 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"')}
helpText={
<FormattedMessage
id='admin.email.smtpServerDescription'
defaultMessage='Location of SMTP email server.'
/>
}
value={this.state.smtpServer}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<TextSetting
id='smtpPort'
label={
<FormattedMessage
id='admin.email.smtpPortTitle'
defaultMessage='SMTP Server Port:'
/>
}
placeholder={Utils.localizeMessage('admin.email.smtpPortExample', 'Ex: "25", "465", "587"')}
helpText={
<FormattedMessage
id='admin.email.smtpPortDescription'
defaultMessage='Port of SMTP email server.'
/>
}
value={this.state.smtpPort}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<BooleanSetting
id='enableSMTPAuth'
label={
<FormattedMessage
id='admin.email.enableSMTPAuthTitle'
defaultMessage='Enable SMTP Authentication: '
/>
}
helpText={[
<FormattedHTMLMessage
key='admin.email.enableSMTPAuthDesc'
id='admin.email.enableSMTPAuthDesc'
defaultMessage='When true, SMTP Authentication is enabled.'
/>
]}
value={this.state.enableSMTPAuth}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<TextSetting
id='smtpUsername'
label={
<FormattedMessage
id='admin.email.smtpUsernameTitle'
defaultMessage='SMTP Server Username:'
/>
}
placeholder={Utils.localizeMessage('admin.email.smtpUsernameExample', 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"')}
helpText={
<FormattedMessage
id='admin.email.smtpUsernameDescription'
defaultMessage=' Obtain this credential from administrator setting up your email server.'
/>
}
value={this.state.smtpUsername}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications || !this.state.enableSMTPAuth}
/>
<TextSetting
id='smtpPassword'
label={
<FormattedMessage
id='admin.email.smtpPasswordTitle'
defaultMessage='SMTP Server Password:'
/>
}
placeholder={Utils.localizeMessage('admin.email.smtpPasswordExample', 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.email.smtpPasswordDescription'
defaultMessage=' Obtain this credential from administrator setting up your email server.'
/>
}
value={this.state.smtpPassword}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications || !this.state.enableSMTPAuth}
/>
<ConnectionSecurityDropdownSettingEmail
value={this.state.connectionSecurity}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<EmailConnectionTest
config={this.props.config}
getConfigFromState={this.getConfigFromState}
disabled={!this.state.sendEmailNotifications}
/>
<BooleanSetting
id='skipServerCertificateVerification'
label={
<FormattedMessage
id='admin.email.skipServerCertificateVerification.title'
defaultMessage='Skip Server Certificate Verification: '
/>
}
helpText={
<FormattedMessage
id='admin.email.skipServerCertificateVerification.description'
defaultMessage='When true, Mattermost will not verify the email server certificate.'
/>
}
value={this.state.skipServerCertificateVerification}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableSecurityFixAlert'
label={
<FormattedMessage
id='admin.service.securityTitle'
defaultMessage='Enable Security Alerts: '
/>
}
helpText={
<FormattedMessage
id='admin.service.securityDesc'
defaultMessage='When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'
/>
}
value={this.state.enableSecurityFixAlert}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,66 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ExternalServiceSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.GoogleDeveloperKey = this.state.googleDeveloperKey;
return config;
}
getStateFromConfig(config) {
return {
googleDeveloperKey: config.ServiceSettings.GoogleDeveloperKey
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.integrations.external'
defaultMessage='External Services'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<TextSetting
id='googleDeveloperKey'
label={
<FormattedMessage
id='admin.service.googleTitle'
defaultMessage='Google API Key:'
/>
}
placeholder={Utils.localizeMessage('admin.service.googleExample', 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"')}
helpText={
<FormattedHTMLMessage
id='admin.service.googleDescription'
defaultMessage='Set this key to enable the display of titles for embedded YouTube video previews. Without the key, YouTube previews will still be created based on hyperlinks appearing in messages or comments but they will not show the video title. View a <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">Google Developers Tutorial</a> for instructions on how to obtain a key.'
/>
}
value={this.state.googleDeveloperKey}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,126 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import Setting from './setting.jsx';
import * as Utils from 'utils/utils.jsx';
export default class FileUploadSetting extends Setting {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
helpText: PropTypes.node,
uploadingText: PropTypes.node,
onSubmit: PropTypes.func.isRequired,
disabled: PropTypes.bool,
fileType: PropTypes.string.isRequired,
error: PropTypes.string
};
}
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
fileName: null,
serverError: props.error
};
}
handleChange() {
const files = this.refs.fileInput.files;
if (files && files.length > 0) {
this.setState({fileSelected: true, fileName: files[0].name});
}
}
handleSubmit(e) {
e.preventDefault();
$(this.refs.upload_button).button('loading');
this.props.onSubmit(this.props.id, this.refs.fileInput.files[0], (error) => {
$(this.refs.upload_button).button('reset');
if (error) {
Utils.clearFileInput(this.refs.fileInput);
}
});
}
render() {
let serverError;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
let btnClass = 'btn';
if (this.state.fileSelected) {
btnClass = 'btn btn-primary';
}
let fileName;
if (this.state.fileName) {
fileName = this.state.fileName;
} else {
fileName = (
<FormattedMessage
id='admin.file_upload.noFile'
defaultMessage='No file uploaded'
/>
);
}
return (
<Setting
label={this.props.label}
helpText={this.props.helpText}
inputId={this.props.id}
>
<div>
<div className='file__upload'>
<button
className='btn btn-default'
disabled={this.props.disabled}
>
<FormattedMessage
id='admin.file_upload.chooseFile'
defaultMessage='Choose File'
/>
</button>
<input
ref='fileInput'
type='file'
disabled={this.props.disabled}
accept={this.props.fileType}
onChange={this.handleChange}
/>
</div>
<button
className={btnClass}
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
ref='upload_button'
data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.uploadingText}`}
>
<FormattedMessage
id='admin.file_upload.uploadFile'
defaultMessage='Upload'
/>
</button>
<div className='help-text no-margin'>
{fileName}
</div>
{serverError}
</div>
</Setting>
);
}
}

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

@@ -1,106 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import crypto from 'crypto';
import {FormattedMessage} from 'react-intl';
export default class GeneratedSetting extends React.Component {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
placeholder: PropTypes.string,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
disabledText: PropTypes.node,
helpText: PropTypes.node.isRequired,
regenerateText: PropTypes.node,
regenerateHelpText: PropTypes.node
};
}
static get defaultProps() {
return {
disabled: false,
regenerateText: (
<FormattedMessage
id='admin.regenerate'
defaultMessage='Regenerate'
/>
)
};
}
constructor(props) {
super(props);
this.regenerate = this.regenerate.bind(this);
}
regenerate(e) {
e.preventDefault();
this.props.onChange(this.props.id, crypto.randomBytes(256).toString('base64').substring(0, 32));
}
render() {
let disabledText = null;
if (this.props.disabled && this.props.disabledText) {
disabledText = (
<div className='admin-console__disabled-text'>
{this.props.disabledText}
</div>
);
}
let regenerateHelpText = null;
if (this.props.regenerateHelpText) {
regenerateHelpText = (
<div className='help-text'>
{this.props.regenerateHelpText}
</div>
);
}
return (
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor={this.props.id}
>
{this.props.label}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id={this.props.id}
placeholder={this.props.placeholder}
value={this.props.value}
disabled={true}
/>
{disabledText}
<div className='help-text'>
{this.props.helpText}
</div>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.regenerate}
disabled={this.props.disabled}
>
{this.props.regenerateText}
</button>
</div>
{regenerateHelpText}
</div>
</div>
);
}
}

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

@@ -1,179 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class GitLabSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.GitLabSettings.Enable = this.state.enable;
config.GitLabSettings.Id = this.state.id;
config.GitLabSettings.Secret = this.state.secret;
config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
return config;
}
getStateFromConfig(config) {
return {
enable: config.GitLabSettings.Enable,
id: config.GitLabSettings.Id,
secret: config.GitLabSettings.Secret,
userApiEndpoint: config.GitLabSettings.UserApiEndpoint,
authEndpoint: config.GitLabSettings.AuthEndpoint,
tokenEndpoint: config.GitLabSettings.TokenEndpoint
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.gitlab'
defaultMessage='GitLab'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.gitlab.enableTitle'
defaultMessage='Enable authentication with GitLab: '
/>
}
helpText={
<div>
<FormattedMessage
id='admin.gitlab.enableDescription'
defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
/>
<br/>
<FormattedHTMLMessage
id='admin.gitlab.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
</div>
}
value={this.state.enable}
onChange={this.handleChange}
/>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.gitlab.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitlab.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
/>
}
value={this.state.id}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.gitlab.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitab.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.gitlab.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex "https://<your-gitlab-url>/api/v3/user"')}
helpText={
<FormattedMessage
id='admin.gitlab.userDescription'
defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.userApiEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.gitlab.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex "https://<your-gitlab-url>/oauth/authorize"')}
helpText={
<FormattedMessage
id='admin.gitlab.authDescription'
defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.authEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.gitlab.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex "https://<your-gitlab-url>/oauth/token"')}
helpText={
<FormattedMessage
id='admin.gitlab.tokenDescription'
defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.tokenEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
</SettingsGroup>
);
}
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getConfig} from 'mattermost-redux/actions/admin';
import * as Selectors from 'mattermost-redux/selectors/entities/admin';
import AdminConsole from './admin_console.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
config: Selectors.getConfig(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getConfig
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AdminConsole);

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

@@ -1,504 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {ConnectionSecurityDropdownSettingLdap} from './connection_security_dropdown_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import {ldapSyncNow, ldapTest} from 'actions/admin_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import RequestButton from './request_button/request_button.jsx';
export default class LdapSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.LdapSettings.Enable = this.state.enable;
config.LdapSettings.LdapServer = this.state.ldapServer;
config.LdapSettings.LdapPort = this.parseIntNonZero(this.state.ldapPort);
config.LdapSettings.ConnectionSecurity = this.state.connectionSecurity;
config.LdapSettings.BaseDN = this.state.baseDN;
config.LdapSettings.BindUsername = this.state.bindUsername;
config.LdapSettings.BindPassword = this.state.bindPassword;
config.LdapSettings.UserFilter = this.state.userFilter;
config.LdapSettings.FirstNameAttribute = this.state.firstNameAttribute;
config.LdapSettings.LastNameAttribute = this.state.lastNameAttribute;
config.LdapSettings.NicknameAttribute = this.state.nicknameAttribute;
config.LdapSettings.EmailAttribute = this.state.emailAttribute;
config.LdapSettings.UsernameAttribute = this.state.usernameAttribute;
config.LdapSettings.PositionAttribute = this.state.positionAttribute;
config.LdapSettings.IdAttribute = this.state.idAttribute;
config.LdapSettings.SyncIntervalMinutes = this.parseIntNonZero(this.state.syncIntervalMinutes);
config.LdapSettings.SkipCertificateVerification = this.state.skipCertificateVerification;
config.LdapSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
config.LdapSettings.MaxPageSize = this.parseInt(this.state.maxPageSize);
config.LdapSettings.LoginFieldName = this.state.loginFieldName;
return config;
}
getStateFromConfig(config) {
return {
enable: config.LdapSettings.Enable,
ldapServer: config.LdapSettings.LdapServer,
ldapPort: config.LdapSettings.LdapPort,
connectionSecurity: config.LdapSettings.ConnectionSecurity,
baseDN: config.LdapSettings.BaseDN,
bindUsername: config.LdapSettings.BindUsername,
bindPassword: config.LdapSettings.BindPassword,
userFilter: config.LdapSettings.UserFilter,
firstNameAttribute: config.LdapSettings.FirstNameAttribute,
lastNameAttribute: config.LdapSettings.LastNameAttribute,
nicknameAttribute: config.LdapSettings.NicknameAttribute,
emailAttribute: config.LdapSettings.EmailAttribute,
usernameAttribute: config.LdapSettings.UsernameAttribute,
positionAttribute: config.LdapSettings.PositionAttribute,
idAttribute: config.LdapSettings.IdAttribute,
syncIntervalMinutes: config.LdapSettings.SyncIntervalMinutes,
skipCertificateVerification: config.LdapSettings.SkipCertificateVerification,
queryTimeout: config.LdapSettings.QueryTimeout,
maxPageSize: config.LdapSettings.MaxPageSize,
loginFieldName: config.LdapSettings.LoginFieldName
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.ldap'
defaultMessage='AD/LDAP'
/>
);
}
renderSettings() {
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true';
if (!licenseEnabled) {
return null;
}
return (
<SettingsGroup>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.ldap.enableTitle'
defaultMessage='Enable sign-in with AD/LDAP:'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.enableDesc'
defaultMessage='When true, Mattermost allows login using AD/LDAP'
/>
}
value={this.state.enable}
onChange={this.handleChange}
/>
<TextSetting
id='ldapServer'
label={
<FormattedMessage
id='admin.ldap.serverTitle'
defaultMessage='AD/LDAP Server:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.serverEx', 'Ex "10.0.0.23"')}
helpText={
<FormattedMessage
id='admin.ldap.serverDesc'
defaultMessage='The domain or IP address of AD/LDAP server.'
/>
}
value={this.state.ldapServer}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='ldapPort'
label={
<FormattedMessage
id='admin.ldap.portTitle'
defaultMessage='AD/LDAP Port:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.portEx', 'Ex "389"')}
helpText={
<FormattedMessage
id='admin.ldap.portDesc'
defaultMessage='The port Mattermost will use to connect to the AD/LDAP server. Default is 389.'
/>
}
value={this.state.ldapPort}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<ConnectionSecurityDropdownSettingLdap
value={this.state.connectionSecurity}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<BooleanSetting
id='skipCertificateVerification'
label={
<FormattedMessage
id='admin.ldap.skipCertificateVerification'
defaultMessage='Skip Certificate Verification:'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.skipCertificateVerificationDesc'
defaultMessage='Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.'
/>
}
value={this.state.skipCertificateVerification}
onChange={this.handleChange}
/>
<TextSetting
id='baseDN'
label={
<FormattedMessage
id='admin.ldap.baseTitle'
defaultMessage='BaseDN:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.baseEx', 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"')}
helpText={
<FormattedMessage
id='admin.ldap.baseDesc'
defaultMessage='The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the AD/LDAP tree.'
/>
}
value={this.state.baseDN}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='bindUsername'
label={
<FormattedMessage
id='admin.ldap.bindUserTitle'
defaultMessage='Bind Username:'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.bindUserDesc'
defaultMessage='The username used to perform the AD/LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the AD/LDAP tree specified in the BaseDN field.'
/>
}
value={this.state.bindUsername}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='bindPassword'
label={
<FormattedMessage
id='admin.ldap.bindPwdTitle'
defaultMessage='Bind Password:'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.bindPwdDesc'
defaultMessage='Password of the user given in "Bind Username".'
/>
}
value={this.state.bindPassword}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='userFilter'
label={
<FormattedMessage
id='admin.ldap.userFilterTitle'
defaultMessage='User Filter:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.userFilterEx', 'Ex. "(objectClass=user)"')}
helpText={
<FormattedMessage
id='admin.ldap.userFilterDisc'
defaultMessage='(Optional) Enter an AD/LDAP Filter to use when searching for user objects. Only the users selected by the query will be able to access Mattermost. For Active Directory, the query to filter out disabled users is (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).'
/>
}
value={this.state.userFilter}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='firstNameAttribute'
label={
<FormattedMessage
id='admin.ldap.firstnameAttrTitle'
defaultMessage='First Name Attribute'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.firstnameAttrEx', 'Ex "givenName"')}
helpText={
<FormattedMessage
id='admin.ldap.firstnameAttrDesc'
defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the first name of users in Mattermost. When set, users will not be able to edit their first name, since it is synchronized with the LDAP server. When left blank, users can set their own first name in Account Settings.'
/>
}
value={this.state.firstNameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='lastNameAttribute'
label={
<FormattedMessage
id='admin.ldap.lastnameAttrTitle'
defaultMessage='Last Name Attribute:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.lastnameAttrEx', 'Ex "sn"')}
helpText={
<FormattedMessage
id='admin.ldap.lastnameAttrDesc'
defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the last name of users in Mattermost. When set, users will not be able to edit their last name, since it is synchronized with the LDAP server. When left blank, users can set their own last name in Account Settings.'
/>
}
value={this.state.lastNameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='nicknameAttribute'
label={
<FormattedMessage
id='admin.ldap.nicknameAttrTitle'
defaultMessage='Nickname Attribute:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')}
helpText={
<FormattedMessage
id='admin.ldap.nicknameAttrDesc'
defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the nickname of users in Mattermost. When set, users will not be able to edit their nickname, since it is synchronized with the LDAP server. When left blank, users can set their own nickname in Account Settings.'
/>
}
value={this.state.nicknameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='positionAttribute'
label={
<FormattedMessage
id='admin.ldap.positionAttrTitle'
defaultMessage='Position Attribute:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.positionAttrEx', 'E.g.: "title"')}
helpText={
<FormattedMessage
id='admin.ldap.positionAttrDesc'
defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the position field in Mattermost.'
/>
}
value={this.state.positionAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='emailAttribute'
label={
<FormattedMessage
id='admin.ldap.emailAttrTitle'
defaultMessage='Email Attribute:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.emailAttrEx', 'Ex "mail" or "userPrincipalName"')}
helpText={
<FormattedMessage
id='admin.ldap.emailAttrDesc'
defaultMessage='The attribute in the AD/LDAP server that will be used to populate the email addresses of users in Mattermost.'
/>
}
value={this.state.emailAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='usernameAttribute'
label={
<FormattedMessage
id='admin.ldap.usernameAttrTitle'
defaultMessage='Username Attribute:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.usernameAttrEx', 'Ex "sAMAccountName"')}
helpText={
<FormattedMessage
id='admin.ldap.uernameAttrDesc'
defaultMessage='The attribute in the AD/LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'
/>
}
value={this.state.usernameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='idAttribute'
label={
<FormattedMessage
id='admin.ldap.idAttrTitle'
defaultMessage='ID Attribute: '
/>
}
placeholder={Utils.localizeMessage('admin.ldap.idAttrEx', 'Ex "sAMAccountName"')}
helpText={
<FormattedMessage
id='admin.ldap.idAttrDesc'
defaultMessage='The attribute in the AD/LDAP server that will be used as a unique identifier in Mattermost. It should be an AD/LDAP attribute with a value that does not change, such as username or uid. If a users ID Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "AD/LDAP Username" field on the sign in page. Normally this attribute is the same as the "Username Attribute" field above. If your team typically uses domain\\username to sign in to other services with AD/LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'
/>
}
value={this.state.idAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='loginFieldName'
label={
<FormattedMessage
id='admin.ldap.loginNameTitle'
defaultMessage='Sign-in Field Default Text:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.loginNameEx', 'Ex "AD/LDAP Username"')}
helpText={
<FormattedMessage
id='admin.ldap.loginNameDesc'
defaultMessage='The placeholder text that appears in the login field on the login page. Defaults to "AD/LDAP Username".'
/>
}
value={this.state.loginFieldName}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='syncIntervalMinutes'
label={
<FormattedMessage
id='admin.ldap.syncIntervalTitle'
defaultMessage='Synchronization Interval (minutes):'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.syncIntervalHelpText'
defaultMessage='AD/LDAP Synchronization updates Mattermost user information to reflect updates on the AD/LDAP server. For example, when a users name changes on the AD/LDAP server, the change updates in Mattermost when synchronization is performed. Accounts removed from or disabled in the AD/LDAP server have their Mattermost accounts set to "Inactive" and have their account sessions revoked. Mattermost performs synchronization on the interval entered. For example, if 60 is entered, Mattermost synchronizes every 60 minutes.'
/>
}
value={this.state.syncIntervalMinutes}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='maxPageSize'
label={
<FormattedMessage
id='admin.ldap.maxPageSizeTitle'
defaultMessage='Maximum Page Size:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.maxPageSizeEx', 'Ex "2000"')}
helpText={
<FormattedMessage
id='admin.ldap.maxPageSizeHelpText'
defaultMessage='The maximum number of users the Mattermost server will request from the AD/LDAP server at one time. 0 is unlimited.'
/>
}
value={this.state.maxPageSize}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='queryTimeout'
label={
<FormattedMessage
id='admin.ldap.queryTitle'
defaultMessage='Query Timeout (seconds):'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.queryEx', 'Ex "60"')}
helpText={
<FormattedMessage
id='admin.ldap.queryDesc'
defaultMessage='The timeout value for queries to the AD/LDAP server. Increase if you are getting timeout errors caused by a slow AD/LDAP server.'
/>
}
value={this.state.queryTimeout}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<RequestButton
requestAction={ldapSyncNow}
helpText={
<FormattedMessage
id='admin.ldap.syncNowHelpText'
defaultMessage='Initiates an AD/LDAP synchronization immediately.'
/>
}
buttonText={
<FormattedMessage
id='admin.ldap.sync_button'
defaultMessage='AD/LDAP Synchronize Now'
/>
}
disabled={!this.state.enable}
showSuccessMessage={false}
errorMessage={{
id: 'admin.ldap.syncFailure',
defaultMessage: 'Sync Failure: {error}'
}}
includeDetailedError={true}
/>
<RequestButton
requestAction={ldapTest}
helpText={
<FormattedMessage
id='admin.ldap.testHelpText'
defaultMessage='Tests if the Mattermost server can connect to the AD/LDAP server specified. See log file for more detailed error messages.'
/>
}
buttonText={
<FormattedMessage
id='admin.ldap.ldap_test_button'
defaultMessage='AD/LDAP Test'
/>
}
disabled={!this.state.enable}
saveNeeded={this.state.saveNeeded}
saveConfigAction={this.doSubmit}
errorMessage={{
id: 'admin.ldap.testFailure',
defaultMessage: 'AD/LDAP Test Failure: {error}'
}}
successMessage={{
id: 'admin.ldap.testSuccess',
defaultMessage: 'AD/LDAP Test Successful'
}}
/>
</SettingsGroup>
);
}
}

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

@@ -1,159 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class LegalAndSupportSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink;
config.SupportSettings.PrivacyPolicyLink = this.state.privacyPolicyLink;
config.SupportSettings.AboutLink = this.state.aboutLink;
config.SupportSettings.HelpLink = this.state.helpLink;
config.SupportSettings.ReportAProblemLink = this.state.reportAProblemLink;
config.SupportSettings.SupportEmail = this.state.supportEmail;
return config;
}
getStateFromConfig(config) {
return {
termsOfServiceLink: config.SupportSettings.TermsOfServiceLink,
privacyPolicyLink: config.SupportSettings.PrivacyPolicyLink,
aboutLink: config.SupportSettings.AboutLink,
helpLink: config.SupportSettings.HelpLink,
reportAProblemLink: config.SupportSettings.ReportAProblemLink,
supportEmail: config.SupportSettings.SupportEmail
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.customization.support'
defaultMessage='Legal and Support'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<TextSetting
id='termsOfServiceLink'
label={
<FormattedMessage
id='admin.support.termsTitle'
defaultMessage='Terms of Service link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.termsDesc'
defaultMessage='Link to the terms under which users may use your online service. By default, this includes the "Mattermost Conditions of Use (End Users)" explaining the terms under which Mattermost software is provided to end users. If you change the default link to add your own terms for using the service you provide, your new terms must include a link to the default terms so end users are aware of the Mattermost Conditions of Use (End User) for Mattermost software.'
/>
}
value={this.state.termsOfServiceLink}
onChange={this.handleChange}
/>
<TextSetting
id='privacyPolicyLink'
label={
<FormattedMessage
id='admin.support.privacyTitle'
defaultMessage='Privacy Policy link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.privacyDesc'
defaultMessage='The URL for the Privacy link on the login and sign-up pages. If this field is empty, the Privacy link is hidden from users.'
/>
}
value={this.state.privacyPolicyLink}
onChange={this.handleChange}
/>
<TextSetting
id='aboutLink'
label={
<FormattedMessage
id='admin.support.aboutTitle'
defaultMessage='About link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.aboutDesc'
defaultMessage='The URL for the About link on the Mattermost login and sign-up pages. If this field is empty, the About link is hidden from users.'
/>
}
value={this.state.aboutLink}
onChange={this.handleChange}
/>
<TextSetting
id='helpLink'
label={
<FormattedMessage
id='admin.support.helpTitle'
defaultMessage='Help link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.helpDesc'
defaultMessage='The URL for the Help link on the Mattermost login page, sign-up pages, and Main Menu. If this field is empty, the Help link is hidden from users.'
/>
}
value={this.state.helpLink}
onChange={this.handleChange}
/>
<TextSetting
id='reportAProblemLink'
label={
<FormattedMessage
id='admin.support.problemTitle'
defaultMessage='Report a Problem link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.problemDesc'
defaultMessage='The URL for the Report a Problem link in the Main Menu. If this field is empty, the link is removed from the Main Menu.'
/>
}
value={this.state.reportAProblemLink}
onChange={this.handleChange}
/>
<TextSetting
id='supportEmail'
label={
<FormattedMessage
id='admin.support.emailTitle'
defaultMessage='Support Email:'
/>
}
helpText={
<FormattedMessage
id='admin.support.emailHelp'
defaultMessage='Email address displayed on email notifications and during tutorial for end users to ask support questions.'
/>
}
value={this.state.supportEmail}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,286 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
import ErrorStore from 'stores/error_store.jsx';
import {uploadLicenseFile, removeLicenseFile} from 'actions/admin_actions.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
const holders = defineMessages({
removing: {
id: 'admin.license.removing',
defaultMessage: 'Removing License...'
},
uploading: {
id: 'admin.license.uploading',
defaultMessage: 'Uploading License...'
}
});
import PropTypes from 'prop-types';
import React from 'react';
class LicenseSettings extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.state = {
fileSelected: false,
fileName: null,
serverError: null
};
}
handleChange() {
const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
if (element.prop('files').length > 0) {
this.setState({fileSelected: true, fileName: element.prop('files')[0].name});
}
}
handleSubmit(e) {
e.preventDefault();
const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
if (element.prop('files').length === 0) {
return;
}
const file = element.prop('files')[0];
$('#upload-button').button('loading');
uploadLicenseFile(
file,
() => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
this.setState({fileSelected: false, fileName: null, serverError: null});
window.location.reload(true);
},
(error) => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
handleRemove(e) {
e.preventDefault();
$('#remove-button').button('loading');
removeLicenseFile(
() => {
$('#remove-button').button('reset');
this.setState({fileSelected: false, fileName: null, serverError: null});
ErrorStore.clearLastError(true);
window.location.reload(true);
},
(error) => {
$('#remove-button').button('reset');
this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
render() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='col-sm-12'><div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div></div>;
}
var btnClass = 'btn';
if (this.state.fileSelected) {
btnClass = 'btn btn-primary';
}
let edition;
let licenseType;
let licenseKey;
const issued = Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10)) + ' ' + Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true);
const startsAt = Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10));
const expiresAt = Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10));
if (global.window.mm_license.IsLicensed === 'true') {
// Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
edition = 'Mattermost Enterprise Edition. Enterprise features on this server have been unlocked with a license key and a valid subscription.';
licenseType = (
<div>
<p>
{'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.\n\nYour subscription details are as follows:'}
</p>
{`Name: ${global.window.mm_license.Name}`}<br/>
{`Company or organization name: ${global.window.mm_license.Company}`}<br/>
{`Number of users: ${global.window.mm_license.Users}`}<br/>
{`License issued: ${issued}`}<br/>
{`Start date of license: ${startsAt}`}<br/>
{`Expiry date of license: ${expiresAt}`}<br/>
<br/>
{'See also '}<a href='https://about.mattermost.com/enterprise-edition-terms/'>{'Enterprise Edition Terms of Service'}</a>{' and '}<a href='https://about.mattermost.com/privacy/'>{'Privacy Policy.'}</a>
</div>
);
licenseKey = (
<div className='col-sm-8'>
<button
className='btn btn-danger'
onClick={this.handleRemove}
id='remove-button'
data-loading-text={'<span class=\'fa fa-refresh icon--rotate\'></span> ' + this.props.intl.formatMessage(holders.removing)}
>
<FormattedMessage
id='admin.license.keyRemove'
defaultMessage='Remove Enterprise License and Downgrade Server'
/>
</button>
<br/>
<br/>
<p className='help-text'>
{'If you migrate servers you may need to remove your license key to install it elsewhere. You can remove the key here, which will revert functionality to that of Team Edition.'}
</p>
</div>
);
} else {
// Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
edition = (
<p>
{'Mattermost Enterprise Edition. Unlock enterprise features in this software through the purchase of a subscription from '}
<a
target='_blank'
rel='noopener noreferrer'
href='https://mattermost.com/'
>
{'https://mattermost.com/'}
</a>
</p>
);
licenseType = 'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.';
let fileName;
if (this.state.fileName) {
fileName = this.state.fileName;
} else {
fileName = (
<FormattedMessage
id='admin.license.noFile'
defaultMessage='No file uploaded'
/>
);
}
licenseKey = (
<div className='col-sm-8'>
<div className='file__upload'>
<button className='btn btn-primary'>
<FormattedMessage
id='admin.license.choose'
defaultMessage='Choose File'
/>
</button>
<input
ref='fileInput'
type='file'
accept='.mattermost-license'
onChange={this.handleChange}
/>
</div>
<button
className={btnClass}
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
id='upload-button'
data-loading-text={'<span class=\'fa fa-refresh icon--rotate\'></span> ' + this.props.intl.formatMessage(holders.uploading)}
>
<FormattedMessage
id='admin.license.upload'
defaultMessage='Upload'
/>
</button>
<div className='help-text no-margin'>
{fileName}
</div>
<br/>
{serverError}
<p className='help-text no-margin'>
<FormattedHTMLMessage
id='admin.license.uploadDesc'
defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a> to learn more about the benefits of Enterprise Edition or to purchase a key.'
/>
</p>
</div>
);
}
return (
<div className='wrapper--fixed'>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.license.title'
defaultMessage='Edition and License'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.license.edition'
defaultMessage='Edition: '
/>
</label>
<div className='col-sm-8'>
{edition}
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.license.type'
defaultMessage='License: '
/>
</label>
<div className='col-sm-8'>
{licenseType}
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.license.key'
defaultMessage='License Key: '
/>
</label>
{licenseKey}
</div>
</form>
</div>
);
}
}
LicenseSettings.propTypes = {
intl: intlShape.isRequired,
config: PropTypes.object
};
export default injectIntl(LicenseSettings);

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

@@ -1,64 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class LinkPreviewsSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.EnableLinkPreviews = this.state.enableLinkPreviews;
return config;
}
getStateFromConfig(config) {
return {
enableLinkPreviews: config.ServiceSettings.EnableLinkPreviews
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.customization.linkPreviews'
defaultMessage='Link Previews'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableLinkPreviews'
label={
<FormattedMessage
id='admin.customization.enableLinkPreviewsTitle'
defaultMessage='Enable Link Previews:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.enableLinkPreviewsDesc'
defaultMessage='Enable users to display a preview of website content below the message, if available. When true, website previews can be enabled from Account Settings > Advanced > Preview pre-release features.'
/>
}
value={this.state.enableLinkPreviews}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,134 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as I18n from 'i18n/i18n.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import MultiSelectSetting from './multiselect_settings.jsx';
export default class LocalizationSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.canSave = this.canSave.bind(this);
const locales = I18n.getAllLanguages();
this.state = Object.assign(this.state, {
hasErrors: false,
languages: Object.keys(locales).map((l) => {
return {value: locales[l].value, text: locales[l].name, order: locales[l].order};
}).sort((a, b) => a.order - b.order)
});
}
canSave() {
return this.state.availableLocales.join(',').indexOf(this.state.defaultClientLocale) !== -1 || this.state.availableLocales.length === 0;
}
getConfigFromState(config) {
config.LocalizationSettings.DefaultServerLocale = this.state.defaultServerLocale;
config.LocalizationSettings.DefaultClientLocale = this.state.defaultClientLocale;
config.LocalizationSettings.AvailableLocales = this.state.availableLocales.join(',');
return config;
}
getStateFromConfig(config) {
return {
defaultServerLocale: config.LocalizationSettings.DefaultServerLocale,
defaultClientLocale: config.LocalizationSettings.DefaultClientLocale,
availableLocales: config.LocalizationSettings.AvailableLocales ? config.LocalizationSettings.AvailableLocales.split(',') : []
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.general.localization'
defaultMessage='Localization'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<DropdownSetting
id='defaultServerLocale'
values={this.state.languages}
label={
<FormattedMessage
id='admin.general.localization.serverLocaleTitle'
defaultMessage='Default Server Language:'
/>
}
value={this.state.defaultServerLocale}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.localization.serverLocaleDescription'
defaultMessage='Default language for system messages and logs. Changing this will require a server restart before taking effect.'
/>
}
/>
<DropdownSetting
id='defaultClientLocale'
values={this.state.languages}
label={
<FormattedMessage
id='admin.general.localization.clientLocaleTitle'
defaultMessage='Default Client Language:'
/>
}
value={this.state.defaultClientLocale}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.localization.clientLocaleDescription'
defaultMessage="Default language for newly created users and pages where the user hasn't logged in."
/>
}
/>
<MultiSelectSetting
id='availableLocales'
values={this.state.languages}
label={
<FormattedMessage
id='admin.general.localization.availableLocalesTitle'
defaultMessage='Available Languages:'
/>
}
selected={this.state.availableLocales}
onChange={this.handleChange}
helpText={
<FormattedHTMLMessage
id='admin.general.localization.availableLocalesDescription'
defaultMessage='Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available). If youre manually adding new languages, the <strong>Default Client Language</strong> must be added before saving this setting.<br /><br />Would like to help with translations? Join the <a href="http://translate.mattermost.com/" target="_blank">Mattermost Translation Server</a> to contribute.'
/>
}
noResultText={
<FormattedMessage
id='admin.general.localization.availableLocalesNoResults'
defaultMessage='No results found'
/>
}
notPresent={
<FormattedMessage
id='admin.general.localization.availableLocalesNotPresent'
defaultMessage='The default client language must be included in the available list'
/>
}
/>
</SettingsGroup>
);
}
}

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

@@ -1,282 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class LogSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.LogSettings.EnableConsole = this.state.enableConsole;
config.LogSettings.ConsoleLevel = this.state.consoleLevel;
config.LogSettings.EnableFile = this.state.enableFile;
config.LogSettings.FileLevel = this.state.fileLevel;
config.LogSettings.FileLocation = this.state.fileLocation;
config.LogSettings.FileFormat = this.state.fileFormat;
config.LogSettings.EnableWebhookDebugging = this.state.enableWebhookDebugging;
config.LogSettings.EnableDiagnostics = this.state.enableDiagnostics;
return config;
}
getStateFromConfig(config) {
return {
enableConsole: config.LogSettings.EnableConsole,
consoleLevel: config.LogSettings.ConsoleLevel,
enableFile: config.LogSettings.EnableFile,
fileLevel: config.LogSettings.FileLevel,
fileLocation: config.LogSettings.FileLocation,
fileFormat: config.LogSettings.FileFormat,
enableWebhookDebugging: config.LogSettings.EnableWebhookDebugging,
enableDiagnostics: config.LogSettings.EnableDiagnostics
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.general.log'
defaultMessage='Logging'
/>
);
}
renderSettings() {
const logLevels = [
{value: 'DEBUG', text: 'DEBUG'},
{value: 'INFO', text: 'INFO'},
{value: 'ERROR', text: 'ERROR'}
];
return (
<SettingsGroup>
<BooleanSetting
id='enableConsole'
label={
<FormattedMessage
id='admin.log.consoleTitle'
defaultMessage='Output logs to console: '
/>
}
helpText={
<FormattedMessage
id='admin.log.consoleDescription'
defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
/>
}
value={this.state.enableConsole}
onChange={this.handleChange}
/>
<DropdownSetting
id='consoleLevel'
values={logLevels}
label={
<FormattedMessage
id='admin.log.levelTitle'
defaultMessage='Console Log Level:'
/>
}
value={this.state.consoleLevel}
onChange={this.handleChange}
disabled={!this.state.enableConsole}
helpText={
<FormattedMessage
id='admin.log.levelDescription'
defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
/>
}
/>
<BooleanSetting
id='enableFile'
label={
<FormattedMessage
id='admin.log.fileTitle'
defaultMessage='Output logs to file: '
/>
}
helpText={
<FormattedMessage
id='admin.log.fileDescription'
defaultMessage='Typically set to true in production. When true, logged events are written to the mattermost.log file in the directory specified in the File Log Directory field. The logs are rotated at 10,000 lines and archived to a file in the same directory, and given a name with a datestamp and serial number. For example, mattermost.2017-03-31.001.'
/>
}
value={this.state.enableFile}
onChange={this.handleChange}
/>
<DropdownSetting
id='fileLevel'
values={logLevels}
label={
<FormattedMessage
id='admin.log.fileLevelTitle'
defaultMessage='File Log Level:'
/>
}
value={this.state.fileLevel}
onChange={this.handleChange}
disabled={!this.state.enableFile}
helpText={
<FormattedMessage
id='admin.log.fileLevelDescription'
defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
/>
}
/>
<TextSetting
id='fileLocation'
label={
<FormattedMessage
id='admin.log.locationTitle'
defaultMessage='File Log Directory:'
/>
}
placeholder={Utils.localizeMessage('admin.log.locationPlaceholder', 'Enter your file location')}
helpText={
<FormattedMessage
id='admin.log.locationDescription'
defaultMessage='The location of the log files. If blank, they are stored in the ./logs directory. The path that you set must exist and Mattermost must have write permissions in it.'
/>
}
value={this.state.fileLocation}
onChange={this.handleChange}
disabled={!this.state.enableFile}
/>
<TextSetting
id='fileFormat'
label={
<FormattedMessage
id='admin.log.formatTitle'
defaultMessage='File Log Format:'
/>
}
placeholder={Utils.localizeMessage('admin.log.formatPlaceholder', 'Enter your file format')}
helpText={this.renderFileFormatHelpText()}
value={this.state.fileFormat}
onChange={this.handleChange}
disabled={!this.state.enableFile}
/>
<BooleanSetting
id='enableWebhookDebugging'
label={
<FormattedMessage
id='admin.log.enableWebhookDebugging'
defaultMessage='Enable Webhook Debugging:'
/>
}
helpText={
<FormattedMessage
id='admin.log.enableWebhookDebuggingDescription'
defaultMessage='You can set this to false to disable the debug logging of all incoming webhook request bodies.'
/>
}
value={this.state.enableWebhookDebugging}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableDiagnostics'
label={
<FormattedMessage
id='admin.log.enableDiagnostics'
defaultMessage='Enable Diagnostics and Error Reporting:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.log.enableDiagnosticsDescription'
defaultMessage='Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <a href="https://about.mattermost.com/default-privacy-policy/" target="_blank">privacy policy</a> to learn more.'
/>
}
value={this.state.enableDiagnostics}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
renderFileFormatHelpText() {
return (
<div>
<FormattedMessage
id='admin.log.formatDescription'
defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
/>
<table
className='table table-bordered'
cellPadding='5'
>
<tbody>
<tr>
<td>{'%T'}</td>
<td>
<FormattedMessage
id='admin.log.formatTime'
defaultMessage='Time (15:04:05 MST)'
/>
</td>
</tr>
<tr>
<td>{'%D'}</td>
<td>
<FormattedMessage
id='admin.log.formatDateLong'
defaultMessage='Date (2006/01/02)'
/>
</td>
</tr>
<tr>
<td>{'%d'}</td>
<td>
<FormattedMessage
id='admin.log.formatDateShort'
defaultMessage='Date (01/02/06)'
/>
</td>
</tr>
<tr>
<td>{'%L'}</td>
<td>
<FormattedMessage
id='admin.log.formatLevel'
defaultMessage='Level (DEBG, INFO, EROR)'
/>
</td>
</tr>
<tr>
<td>{'%S'}</td>
<td>
<FormattedMessage
id='admin.log.formatSource'
defaultMessage='Source'
/>
</td>
</tr>
<tr>
<td>{'%M'}</td>
<td>
<FormattedMessage
id='admin.log.formatMessage'
defaultMessage='Message'
/>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}

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

@@ -1,25 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {updateUserRoles} from 'mattermost-redux/actions/users';
import ManageRolesModal from './manage_roles_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
userAccessTokensEnabled: state.entities.admin.config.ServiceSettings.EnableUserAccessTokens
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
updateUserRoles
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ManageRolesModal);

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

@@ -1,349 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as UserUtils from 'mattermost-redux/utils/user_utils';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import React from 'react';
import {Modal} from 'react-bootstrap';
import PropTypes from 'prop-types';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
function getStateFromProps(props) {
const roles = props.user && props.user.roles ? props.user.roles : '';
return {
error: null,
hasPostAllRole: UserUtils.hasPostAllRole(roles),
hasPostAllPublicRole: UserUtils.hasPostAllPublicRole(roles),
hasUserAccessTokenRole: UserUtils.hasUserAccessTokenRole(roles),
isSystemAdmin: UserUtils.isSystemAdmin(roles)
};
}
export default class ManageRolesModal extends React.PureComponent {
static propTypes = {
/**
* Set to render the modal
*/
show: PropTypes.bool.isRequired,
/**
* The user the roles are being managed for
*/
user: PropTypes.object,
/**
* Set if user access tokens are enabled
*/
userAccessTokensEnabled: PropTypes.bool.isRequired,
/**
* Function called when modal is dismissed
*/
onModalDismissed: PropTypes.func.isRequired,
actions: PropTypes.shape({
/**
* Function to update a user's roles
*/
updateUserRoles: PropTypes.func.isRequired
}).isRequired
};
constructor(props) {
super(props);
this.state = getStateFromProps(props);
}
componentWillReceiveProps(nextProps) {
const user = this.props.user || {};
const nextUser = nextProps.user || {};
if (user.id !== nextUser.id) {
this.setState(getStateFromProps(nextProps));
}
}
handleError = (error) => {
this.setState({
error
});
}
handleSystemAdminChange = (e) => {
if (e.target.name === 'systemadmin') {
this.setState({isSystemAdmin: true});
} else if (e.target.name === 'systemmember') {
this.setState({isSystemAdmin: false});
}
};
handleUserAccessTokenChange = (e) => {
this.setState({
hasUserAccessTokenRole: e.target.checked
});
};
handlePostAllChange = (e) => {
this.setState({
hasPostAllRole: e.target.checked
});
};
handlePostAllPublicChange = (e) => {
this.setState({
hasPostAllPublicRole: e.target.checked
});
};
trackRoleChanges = (roles, oldRoles) => {
if (UserUtils.hasUserAccessTokenRole(roles) && !UserUtils.hasUserAccessTokenRole(oldRoles)) {
trackEvent('actions', 'add_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
} else if (!UserUtils.hasUserAccessTokenRole(roles) && UserUtils.hasUserAccessTokenRole(oldRoles)) {
trackEvent('actions', 'remove_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
}
if (UserUtils.hasPostAllRole(roles) && !UserUtils.hasPostAllRole(oldRoles)) {
trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_ROLE});
} else if (!UserUtils.hasPostAllRole(roles) && UserUtils.hasPostAllRole(oldRoles)) {
trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_ROLE});
}
if (UserUtils.hasPostAllPublicRole(roles) && !UserUtils.hasPostAllPublicRole(oldRoles)) {
trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
} else if (!UserUtils.hasPostAllPublicRole(roles) && UserUtils.hasPostAllPublicRole(oldRoles)) {
trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
}
}
handleSave = async () => {
this.setState({error: null});
let roles = General.SYSTEM_USER_ROLE;
if (this.state.isSystemAdmin) {
roles += ' ' + General.SYSTEM_ADMIN_ROLE;
} else if (this.state.hasUserAccessTokenRole) {
roles += ' ' + General.SYSTEM_USER_ACCESS_TOKEN_ROLE;
if (this.state.hasPostAllRole) {
roles += ' ' + General.SYSTEM_POST_ALL_ROLE;
} else if (this.state.hasPostAllPublicRole) {
roles += ' ' + General.SYSTEM_POST_ALL_PUBLIC_ROLE;
}
}
const data = await this.props.actions.updateUserRoles(this.props.user.id, roles);
this.trackRoleChanges(roles, this.props.user.roles);
if (data) {
this.props.onModalDismissed();
} else {
this.handleError(
<FormattedMessage
id='admin.manage_roles.saveError'
defaultMessage='Unable to save roles.'
/>
);
}
}
renderContents = () => {
const {user} = this.props;
if (user == null) {
return <div/>;
}
let name = UserUtils.getFullName(user);
if (name) {
name += ` (@${user.username})`;
} else {
name = `@${user.username}`;
}
let additionalRoles;
if (this.state.hasUserAccessTokenRole || this.state.isSystemAdmin) {
additionalRoles = (
<div>
<p>
<FormattedHTMLMessage
id='admin.manage_roles.additionalRoles'
defaultMessage='Select additional permissions for the account. <a href="https://about.mattermost.com/default-permissions" target="_blank">Read more about roles and permissions</a>.'
/>
</p>
<div className='checkbox'>
<label>
<input
type='checkbox'
ref='postall'
checked={this.state.hasPostAllRole || this.state.isSystemAdmin}
disabled={this.state.isSystemAdmin}
onChange={this.handlePostAllChange}
/>
<strong>
<FormattedMessage
id='admin.manage_roles.postAllRoleTitle'
defaultMessage='post:all'
/>
</strong>
<FormattedMessage
id='admin.manage_roles.postAllRole'
defaultMessage='Access to post to all Mattermost channels including direct messages.'
/>
</label>
</div>
<div className='checkbox'>
<label>
<input
type='checkbox'
ref='postallpublic'
checked={this.state.hasPostAllPublicRole || this.state.hasPostAllRole || this.state.isSystemAdmin}
disabled={this.state.hasPostAllRole || this.state.isSystemAdmin}
onChange={this.handlePostAllPublicChange}
/>
<strong>
<FormattedMessage
id='admin.manage_roles.postAllPublicRoleTitle'
defaultMessage='post:channels'
/>
</strong>
<FormattedMessage
id='admin.manage_roles.postAllPublicRole'
defaultMessage='Access to post to all Mattermost public channels.'
/>
</label>
</div>
</div>
);
}
let userAccessTokenContent;
if (this.props.userAccessTokensEnabled) {
userAccessTokenContent = (
<div>
<div className='checkbox'>
<label>
<input
type='checkbox'
ref='postall'
checked={this.state.hasUserAccessTokenRole || this.state.isSystemAdmin}
disabled={this.state.isSystemAdmin}
onChange={this.handleUserAccessTokenChange}
/>
<FormattedHTMLMessage
id='admin.manage_roles.allowUserAccessTokens'
defaultMessage='Allow this account to generate <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a>.'
/>
</label>
</div>
<div className='member-row--padded'>
{additionalRoles}
</div>
</div>
);
}
return (
<div>
<div className='manage-teams__user'>
<img
className='manage-teams__profile-picture'
src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
/>
<div className='manage-teams__info'>
<div className='manage-teams__name'>
{name}
</div>
<div className='manage-teams__email'>
{user.email}
</div>
</div>
</div>
<div>
<div className='manage-row--inner'>
<div className='radio-inline'>
<label>
<input
name='systemadmin'
type='radio'
checked={this.state.isSystemAdmin}
onChange={this.handleSystemAdminChange}
/>
<FormattedMessage
id='admin.manage_roles.systemAdmin'
defaultMessage='System Admin'
/>
</label>
</div>
<div className='radio-inline'>
<label>
<input
name='systemmember'
type='radio'
checked={!this.state.isSystemAdmin}
onChange={this.handleSystemAdminChange}
/>
<FormattedMessage
id='admin.manage_roles.systemMember'
defaultMessage='Member'
/>
</label>
</div>
</div>
{userAccessTokenContent}
</div>
</div>
);
}
render() {
return (
<Modal
show={this.props.show}
onHide={this.props.onModalDismissed}
dialogClassName='manage-teams'
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='admin.manage_roles.manageRolesTitle'
defaultMessage='Manage Roles'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.renderContents()}
{this.state.error}
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-link'
onClick={this.props.onModalDismissed}
>
<FormattedMessage
id='admin.manage_roles.cancel'
defaultMessage='Cancel'
/>
</button>
<button
type='button'
className='btn btn-primary'
onClick={this.handleSave}
>
<FormattedMessage
id='admin.manage_roles.save'
defaultMessage='Save'
/>
</button>
</Modal.Footer>
</Modal>
);
}
}

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

@@ -1,148 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Dropdown, MenuItem} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {updateTeamMemberRoles, removeUserFromTeam} from 'actions/team_actions.jsx';
import * as Utils from 'utils/utils.jsx';
export default class ManageTeamsDropdown extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired,
teamMember: PropTypes.object.isRequired,
onError: PropTypes.func.isRequired,
onMemberChange: PropTypes.func.isRequired,
onMemberRemove: PropTypes.func.isRequired
};
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.makeTeamAdmin = this.makeTeamAdmin.bind(this);
this.makeMember = this.makeMember.bind(this);
this.removeFromTeam = this.removeFromTeam.bind(this);
this.handleMemberChange = this.handleMemberChange.bind(this);
this.handleMemberRemove = this.handleMemberRemove.bind(this);
this.state = {
show: false
};
}
toggleDropdown() {
this.setState((prevState) => {
return {show: !prevState.show};
});
}
makeTeamAdmin() {
updateTeamMemberRoles(
this.props.teamMember.team_id,
this.props.user.id,
'team_user team_admin',
this.handleMemberChange,
this.props.onError
);
}
makeMember() {
updateTeamMemberRoles(
this.props.teamMember.team_id,
this.props.user.id,
'team_user',
this.handleMemberChange,
this.props.onError
);
}
removeFromTeam() {
removeUserFromTeam(
this.props.teamMember.team_id,
this.props.user.id,
this.handleMemberRemove,
this.props.onError
);
}
handleMemberChange() {
this.props.onMemberChange(this.props.teamMember.team_id);
}
handleMemberRemove() {
this.props.onMemberRemove(this.props.teamMember.team_id);
}
render() {
const isTeamAdmin = Utils.isAdmin(this.props.teamMember.roles);
let title;
if (isTeamAdmin) {
title = Utils.localizeMessage('admin.user_item.teamAdmin', 'Team Admin');
} else {
title = Utils.localizeMessage('admin.user_item.teamMember', 'Team Member');
}
let makeTeamAdmin = null;
if (!isTeamAdmin) {
makeTeamAdmin = (
<MenuItem
id='makeTeamAdmin'
onSelect={this.makeTeamAdmin}
>
<FormattedMessage
id='admin.user_item.makeTeamAdmin'
defaultMessage='Make Team Admin'
/>
</MenuItem>
);
}
let makeMember = null;
if (isTeamAdmin) {
makeMember = (
<MenuItem
id='makeMember'
onSelect={this.makeMember}
>
<FormattedMessage
id='admin.user_item.makeMember'
defaultMessage='Make Member'
/>
</MenuItem>
);
}
return (
<Dropdown
id={`manage-teams-${this.props.user.id}-${this.props.teamMember.team_id}`}
open={this.state.show}
onToggle={this.toggleDropdown}
>
<Dropdown.Toggle useAnchor={true}>
{title}
</Dropdown.Toggle>
<Dropdown.Menu>
{makeTeamAdmin}
{makeMember}
<MenuItem
id='removeFromTeam'
onSelect={this.removeFromTeam}
>
<FormattedMessage
id='team_members_dropdown.leave_team'
defaultMessage='Remove from Team'
/>
</MenuItem>
</Dropdown.Menu>
</Dropdown>
);
}
}

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

@@ -1,218 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import * as TeamActions from 'actions/team_actions.jsx';
import {Client4} from 'mattermost-redux/client';
import LoadingScreen from 'components/loading_screen.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as Utils from 'utils/utils.jsx';
import ManageTeamsDropdown from './manage_teams_dropdown.jsx';
import RemoveFromTeamButton from './remove_from_team_button.jsx';
export default class ManageTeamsModal extends React.Component {
static propTypes = {
onModalDismissed: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
user: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
error: null,
teams: null,
teamMembers: null
};
}
componentDidMount() {
if (this.props.user) {
this.loadTeamsAndTeamMembers();
}
}
componentWillReceiveProps(nextProps) {
const userId = this.props.user ? this.props.user.id : '';
const nextUserId = nextProps.user ? nextProps.user.id : '';
if (userId !== nextUserId) {
this.setState({
teams: null,
teamMembers: null
});
if (nextProps.user) {
this.loadTeamsAndTeamMembers(nextProps.user);
}
}
}
loadTeamsAndTeamMembers = (user = this.props.user) => {
TeamActions.getTeamsForUser(user.id, (teams) => {
this.setState({
teams: teams.sort(sortTeamsByDisplayName)
});
});
TeamActions.getTeamMembersForUser(user.id, (teamMembers) => {
this.setState({
teamMembers
});
});
}
handleError = (error) => {
this.setState({
error
});
}
handleMemberChange = () => {
TeamActions.getTeamMembersForUser(this.props.user.id, (teamMembers) => {
this.setState({
teamMembers
});
});
}
handleMemberRemove = (teamId) => {
this.setState({
teams: this.state.teams.filter((team) => team.id !== teamId),
teamMembers: this.state.teamMembers.filter((teamMember) => teamMember.team_id !== teamId)
});
}
renderContents = () => {
const {user} = this.props;
const {teams, teamMembers} = this.state;
if (!user) {
return <LoadingScreen/>;
}
const isSystemAdmin = Utils.isAdmin(user.roles);
let name = Utils.getFullName(user);
if (name) {
name += ` (@${user.username})`;
} else {
name = `@${user.username}`;
}
let teamList;
if (teams && teamMembers) {
teamList = teams.map((team) => {
const teamMember = teamMembers.find((member) => member.team_id === team.id);
if (!teamMember) {
return null;
}
let action;
if (isSystemAdmin) {
action = (
<RemoveFromTeamButton
user={user}
team={team}
onError={this.handleError}
onMemberRemove={this.handleMemberRemove}
/>
);
} else {
action = (
<ManageTeamsDropdown
user={user}
team={team}
teamMember={teamMember}
onError={this.handleError}
onMemberChange={this.handleMemberChange}
onMemberRemove={this.handleMemberRemove}
/>
);
}
return (
<div
key={team.id}
className='manage-teams__team'
>
<div className='manage-teams__team-name'>
{team.display_name}
</div>
<div className='manage-teams__team-actions'>
{action}
</div>
</div>
);
});
} else {
teamList = <LoadingScreen/>;
}
let systemAdminIndicator = null;
if (isSystemAdmin) {
systemAdminIndicator = (
<div className='manage-teams__system-admin'>
<FormattedMessage
id='admin.user_item.sysAdmin'
defaultMessage='System Admin'
/>
</div>
);
}
return (
<div>
<div className='manage-teams__user'>
<img
className='manage-teams__profile-picture'
src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
/>
<div className='manage-teams__info'>
<div className='manage-teams__name'>
{name}
</div>
<div className='manage-teams__email'>
{user.email}
</div>
</div>
{systemAdminIndicator}
</div>
<div className='manage-teams__teams'>
{teamList}
</div>
</div>
);
}
render() {
return (
<Modal
show={this.props.show}
onHide={this.props.onModalDismissed}
dialogClassName='manage-teams'
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='admin.user_item.manageTeams'
defaultMessage='Manage Teams'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.renderContents()}
</Modal.Body>
</Modal>
);
}
}

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

@@ -1,54 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {removeUserFromTeam} from 'actions/team_actions.jsx';
export default class RemoveFromTeamButton extends React.PureComponent {
static propTypes = {
onError: PropTypes.func.isRequired,
onMemberRemove: PropTypes.func.isRequired,
team: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleMemberRemove = this.handleMemberRemove.bind(this);
}
handleClick(e) {
e.preventDefault();
removeUserFromTeam(
this.props.team.id,
this.props.user.id,
this.handleMemberRemove,
this.props.onError
);
}
handleMemberRemove() {
this.props.onMemberRemove(this.props.team.id);
}
render() {
return (
<button
className='btn btn-danger'
onClick={this.handleClick}
>
<FormattedMessage
id='team_members_dropdown.leave_team'
defaultMessage='Remove from Team'
/>
</button>
);
}
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getUserAccessTokensForUser} from 'mattermost-redux/actions/users';
import ManageTokensModal from './manage_tokens_modal.jsx';
function mapStateToProps(state, ownProps) {
const userId = ownProps.user ? ownProps.user.id : '';
return {
...ownProps,
userAccessTokens: state.entities.admin.userAccessTokens[userId]
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUserAccessTokensForUser
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ManageTokensModal);

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

@@ -1,181 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import RevokeTokenButton from 'components/admin_console/revoke_token_button';
import {Client4} from 'mattermost-redux/client';
import * as UserUtils from 'mattermost-redux/utils/user_utils';
import React from 'react';
import {Modal} from 'react-bootstrap';
import PropTypes from 'prop-types';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
export default class ManageTokensModal extends React.PureComponent {
static propTypes = {
/**
* Set to render the modal
*/
show: PropTypes.bool.isRequired,
/**
* The user the roles are being managed for
*/
user: PropTypes.object,
/**
* The personal access tokens for a user, object with token ids as keys
*/
userAccessTokens: PropTypes.object,
/**
* Function called when modal is dismissed
*/
onModalDismissed: PropTypes.func.isRequired,
actions: PropTypes.shape({
/**
* Function to get a user's access tokens
*/
getUserAccessTokensForUser: PropTypes.func.isRequired
}).isRequired
};
constructor(props) {
super(props);
this.state = {error: null};
}
componentWillReceiveProps(nextProps) {
const userId = this.props.user ? this.props.user.id : null;
const nextUserId = nextProps.user ? nextProps.user.id : null;
if (nextUserId && nextUserId !== userId) {
this.props.actions.getUserAccessTokensForUser(nextUserId, 0, 200);
}
}
handleError = (error) => {
this.setState({
error
});
}
renderContents = () => {
const {user, userAccessTokens} = this.props;
if (!user) {
return <LoadingScreen/>;
}
let name = UserUtils.getFullName(user);
if (name) {
name += ` (@${user.username})`;
} else {
name = `@${user.username}`;
}
let tokenList;
if (userAccessTokens) {
const userAccessTokensList = Object.values(userAccessTokens);
if (userAccessTokensList.length === 0) {
tokenList = (
<div className='manage-row__empty'>
<FormattedMessage
id='admin.manage_tokens.userAccessTokensNone'
defaultMessage='No personal access tokens.'
/>
</div>
);
} else {
tokenList = userAccessTokensList.map((token) => {
return (
<div
key={token.id}
className='manage-teams__team'
>
<div className='manage-teams__team-name'>
<div className='whitespace--nowrap overflow--ellipsis'>
<FormattedMessage
id='admin.manage_tokens.userAccessTokensNameLabel'
defaultMessage='Token Description: '
/>
{token.description}
</div>
<div className='whitespace--nowrap overflow--ellipsis'>
<FormattedMessage
id='admin.manage_tokens.userAccessTokensIdLabel'
defaultMessage='Token ID: '
/>
{token.id}
</div>
</div>
<div className='manage-teams__team-actions'>
<RevokeTokenButton
tokenId={token.id}
onError={this.handleError}
/>
</div>
</div>
);
});
}
} else {
tokenList = <LoadingScreen/>;
}
return (
<div>
<div className='manage-teams__user'>
<img
className='manage-teams__profile-picture'
src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
/>
<div className='manage-teams__info'>
<div className='manage-teams__name'>
{name}
</div>
<div className='manage-teams__email'>
{user.email}
</div>
</div>
</div>
<div className='padding-top x2'>
<FormattedHTMLMessage
id='admin.manage_tokens.userAccessTokensDescription'
defaultMessage='Personal access tokens function similar to session tokens and can be used by integrations to <a href="https://about.mattermost.com/default-api-authentication" target="_blank">interact with this Mattermost server</a>. Learn more about <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">personal access tokens</a>.'
/>
</div>
<div className='manage-teams__teams'>
{tokenList}
</div>
</div>
);
}
render() {
return (
<Modal
show={this.props.show}
onHide={this.props.onModalDismissed}
dialogClassName='manage-teams'
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='admin.manage_tokens.manageTokensTitle'
defaultMessage='Manage Personal Access Tokens'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.renderContents()}
{this.state.error}
</Modal.Body>
</Modal>
);
}
}

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

@@ -1,94 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import TextSetting from './text_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import * as Utils from 'utils/utils.jsx';
export default class MetricsSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.MetricsSettings.Enable = this.state.enable;
config.MetricsSettings.ListenAddress = this.state.listenAddress;
return config;
}
getStateFromConfig(config) {
const settings = config.MetricsSettings;
return {
enable: settings.Enable,
listenAddress: settings.ListenAddress
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.advance.metrics'
defaultMessage='Performance Monitoring'
/>
);
}
renderSettings() {
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Metrics === 'true';
if (!licenseEnabled) {
return null;
}
return (
<SettingsGroup>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.metrics.enableTitle'
defaultMessage='Enable Performance Monitoring:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.metrics.enableDescription'
defaultMessage='When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href="http://docs.mattermost.com/deployment/metrics.html" target="_blank">documentation</a> to learn more about configuring performance monitoring for Mattermost.'
/>
}
value={this.state.enable}
onChange={this.handleChange}
/>
<TextSetting
id='listenAddress'
label={
<FormattedMessage
id='admin.metrics.listenAddressTitle'
defaultMessage='Listen Address:'
/>
}
placeholder={Utils.localizeMessage('admin.metrics.listenAddressEx', 'Ex ":8067"')}
helpText={
<FormattedMessage
id='admin.metrics.listenAddressDesc'
defaultMessage='The address the server will listen on to expose performance metrics.'
/>
}
value={this.state.listenAddress}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,97 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AdminSettings from './admin_settings.jsx';
import SettingsGroup from './settings_group.jsx';
import BooleanSetting from './boolean_setting.jsx';
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
export default class MfaSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableMultifactorAuthentication: props.config.ServiceSettings.EnableMultifactorAuthentication,
enforceMultifactorAuthentication: props.config.ServiceSettings.EnforceMultifactorAuthentication
});
}
getConfigFromState(config) {
config.ServiceSettings.EnableMultifactorAuthentication = this.state.enableMultifactorAuthentication;
config.ServiceSettings.EnforceMultifactorAuthentication = this.state.enableMultifactorAuthentication && this.state.enforceMultifactorAuthentication;
return config;
}
getStateFromConfig(config) {
return {
enableMultifactorAuthentication: config.ServiceSettings.EnableMultifactorAuthentication,
enforceMultifactorAuthentication: config.ServiceSettings.EnableMultifactorAuthentication && config.ServiceSettings.EnforceMultifactorAuthentication
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.mfa.title'
defaultMessage='Multi-factor Authentication'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<FormattedHTMLMessage
id='admin.mfa.bannerDesc'
defaultMessage="<a href='https://docs.mattermost.com/deployment/auth.html' target='_blank'>Multi-factor authentication</a> is available for accounts with AD/LDAP or email login. If other login methods are used, MFA should be configured with the authentication provider."
/>
</div>
</div>
<BooleanSetting
id='enableMultifactorAuthentication'
label={
<FormattedMessage
id='admin.service.mfaTitle'
defaultMessage='Enable Multi-factor Authentication:'
/>
}
helpText={
<FormattedMessage
id='admin.service.mfaDesc'
defaultMessage='When true, users with AD/LDAP or email login can add multi-factor authentication to their account using Google Authenticator.'
/>
}
value={this.state.enableMultifactorAuthentication}
onChange={this.handleChange}
/>
<BooleanSetting
id='enforceMultifactorAuthentication'
label={
<FormattedMessage
id='admin.service.enforceMfaTitle'
defaultMessage='Enforce Multi-factor Authentication:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.service.enforceMfaDesc'
defaultMessage="When true, <a href='https://docs.mattermost.com/deployment/auth.html' target='_blank'>multi-factor authentication</a> is required for login. New users will be required to configure MFA on signup. Logged in users without MFA configured are redirected to the MFA setup page until configuration is complete.<br/><br/>If your system has users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost."
/>
}
disabled={!this.state.enableMultifactorAuthentication}
value={this.state.enforceMultifactorAuthentication}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,81 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import ReactSelect from 'react-select';
import Setting from './setting.jsx';
import FormError from 'components/form_error.jsx';
export default class MultiSelectSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {error: false};
}
handleChange(newValue) {
const values = newValue.map((n) => {
return n.value;
});
if (this.props.selected.length > 0 && this.props.mustBePresent && values.join(',').indexOf(this.props.mustBePresent) === -1) {
this.setState({error: this.props.notPresent});
} else {
this.props.onChange(this.props.id, values);
this.setState({error: false});
}
}
componentWillReceiveProps(newProps) {
if (newProps.selected.length > 0 && newProps.mustBePresent && newProps.selected.join(',').indexOf(newProps.mustBePresent) === -1) {
this.setState({error: this.props.notPresent});
} else {
this.setState({error: false});
}
}
render() {
return (
<Setting
label={this.props.label}
inputId={this.props.id}
helpText={this.props.helpText}
>
<ReactSelect
id={this.props.id}
multi={true}
labelKey='text'
options={this.props.values}
joinValues={true}
clearable={false}
disabled={this.props.disabled}
noResultsText={this.props.noResultText}
onChange={this.handleChange}
value={this.props.selected}
/>
<FormError error={this.state.error}/>
</Setting>
);
}
}
MultiSelectSetting.defaultProps = {
disabled: false
};
MultiSelectSetting.propTypes = {
id: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
selected: PropTypes.array.isRequired,
mustBePresent: PropTypes.string,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
helpText: PropTypes.node,
noResultText: PropTypes.node,
errorText: PropTypes.node,
notPresent: PropTypes.node
};

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

@@ -1,102 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class NativeAppLinkSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.NativeAppSettings.AppDownloadLink = this.state.appDownloadLink;
config.NativeAppSettings.AndroidAppDownloadLink = this.state.androidAppDownloadLink;
config.NativeAppSettings.IosAppDownloadLink = this.state.iosAppDownloadLink;
return config;
}
getStateFromConfig(config) {
return {
appDownloadLink: config.NativeAppSettings.AppDownloadLink,
androidAppDownloadLink: config.NativeAppSettings.AndroidAppDownloadLink,
iosAppDownloadLink: config.NativeAppSettings.IosAppDownloadLink
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.customization.nativeAppLinks'
defaultMessage='Mattermost App Links'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<TextSetting
id='appDownloadLink'
label={
<FormattedMessage
id='admin.customization.appDownloadLinkTitle'
defaultMessage='Mattermost Apps Download Page Link:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.appDownloadLinkDesc'
defaultMessage='Add a link to a download page for the Mattermost apps. When a link is present, an option to "Download Mattermost Apps" will be added in the Main Menu so users can find the download page. Leave this field blank to hide the option from the Main Menu.'
/>
}
value={this.state.appDownloadLink}
onChange={this.handleChange}
/>
<TextSetting
id='androidAppDownloadLink'
label={
<FormattedMessage
id='admin.customization.androidAppDownloadLinkTitle'
defaultMessage='Android App Download Link:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.androidAppDownloadLinkDesc'
defaultMessage='Add a link to download the Android app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.'
/>
}
value={this.state.androidAppDownloadLink}
onChange={this.handleChange}
/>
<TextSetting
id='iosAppDownloadLink'
label={
<FormattedMessage
id='admin.customization.iosAppDownloadLinkTitle'
defaultMessage='iOS App Download Link:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.iosAppDownloadLinkDesc'
defaultMessage='Add a link to download the iOS app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.'
/>
}
value={this.state.iosAppDownloadLink}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,430 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import AdminSettings from './admin_settings.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import React from 'react';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
export default class OAuthSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.getStateFromConfig = this.getStateFromConfig.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.renderOffice365 = this.renderOffice365.bind(this);
this.renderGoogle = this.renderGoogle.bind(this);
this.renderGitLab = this.renderGitLab.bind(this);
this.changeType = this.changeType.bind(this);
}
getConfigFromState(config) {
config.GitLabSettings.Enable = false;
config.GoogleSettings.Enable = false;
config.Office365Settings.Enable = false;
if (this.state.oauthType === Constants.GITLAB_SERVICE) {
config.GitLabSettings.Enable = true;
config.GitLabSettings.Id = this.state.id;
config.GitLabSettings.Secret = this.state.secret;
config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
}
if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
config.GoogleSettings.Enable = true;
config.GoogleSettings.Id = this.state.id;
config.GoogleSettings.Secret = this.state.secret;
config.GoogleSettings.UserApiEndpoint = 'https://www.googleapis.com/plus/v1/people/me';
config.GoogleSettings.AuthEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
config.GoogleSettings.TokenEndpoint = 'https://www.googleapis.com/oauth2/v4/token';
config.GoogleSettings.Scope = 'profile email';
}
if (this.state.oauthType === Constants.OFFICE365_SERVICE) {
config.Office365Settings.Enable = true;
config.Office365Settings.Id = this.state.id;
config.Office365Settings.Secret = this.state.secret;
config.Office365Settings.UserApiEndpoint = 'https://graph.microsoft.com/v1.0/me';
config.Office365Settings.AuthEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
config.Office365Settings.TokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
config.Office365Settings.Scope = 'User.Read';
}
return config;
}
getStateFromConfig(config) {
this.config = config;
let oauthType = 'off';
let settings = {};
if (config.GitLabSettings.Enable) {
oauthType = Constants.GITLAB_SERVICE;
settings = config.GitLabSettings;
} else if (config.GoogleSettings.Enable) {
oauthType = Constants.GOOGLE_SERVICE;
settings = config.GoogleSettings;
} else if (config.Office365Settings.Enable) {
oauthType = Constants.OFFICE365_SERVICE;
settings = config.Office365Settings;
}
return {
oauthType,
id: settings.Id,
secret: settings.Secret,
userApiEndpoint: settings.UserApiEndpoint,
authEndpoint: settings.AuthEndpoint,
tokenEndpoint: settings.TokenEndpoint
};
}
changeType(id, value) {
let settings = {};
if (value === Constants.GITLAB_SERVICE) {
settings = this.config.GitLabSettings;
} else if (value === Constants.GOOGLE_SERVICE) {
settings = this.config.GoogleSettings;
} else if (value === Constants.OFFICE365_SERVICE) {
settings = this.config.Office365Settings;
}
this.setState({
id: settings.Id,
secret: settings.Secret,
userApiEndpoint: settings.UserApiEndpoint,
authEndpoint: settings.AuthEndpoint,
tokenEndpoint: settings.TokenEndpoint
});
this.handleChange(id, value);
}
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.oauth'
defaultMessage='OAuth 2.0'
/>
);
}
renderGoogle() {
return (
<div>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.google.clientIdTitle'
defaultMessage='Client ID:'
/>
}
placeholder={Utils.localizeMessage('admin.google.clientIdExample', 'Ex "7602141235235-url0fhs1mayfasbmop5qlfns8dh4.apps.googleusercontent.com"')}
helpText={
<FormattedMessage
id='admin.google.clientIdDescription'
defaultMessage='The Client ID you received when registering your application with Google.'
/>
}
value={this.state.id}
onChange={this.handleChange}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.google.clientSecretTitle'
defaultMessage='Client Secret:'
/>
}
placeholder={Utils.localizeMessage('admin.google.clientSecretExample', 'Ex "H8sz0Az-dDs2p15-7QzD231"')}
helpText={
<FormattedMessage
id='admin.google.clientSecretDescription'
defaultMessage='The Client Secret you received when registering your application with Google.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.google.userTitle'
defaultMessage='User API Endpoint:'
/>
}
value='https://www.googleapis.com/plus/v1/people/me'
disabled={true}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.google.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
value='https://accounts.google.com/o/oauth2/v2/auth'
disabled={true}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.google.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
value='https://www.googleapis.com/oauth2/v4/token'
disabled={true}
/>
</div>
);
}
renderOffice365() {
return (
<div>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.office365.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.office365.clientIdExample', 'Ex "adf3sfa2-ag3f-sn4n-ids0-sh1hdax192qq"')}
helpText={
<FormattedMessage
id='admin.office365.clientIdDescription'
defaultMessage='The Application/Client ID you received when registering your application with Microsoft.'
/>
}
value={this.state.id}
onChange={this.handleChange}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.office365.clientSecretTitle'
defaultMessage='Application Secret Password:'
/>
}
placeholder={Utils.localizeMessage('admin.office365.clientSecretExample', 'Ex "shAieM47sNBfgl20f8ci294"')}
helpText={
<FormattedMessage
id='admin.office365.clientSecretDescription'
defaultMessage='The Application Secret Password you generated when registering your application with Microsoft.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.office365.userTitle'
defaultMessage='User API Endpoint:'
/>
}
value='https://graph.microsoft.com/v1.0/me'
disabled={true}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.office365.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
value='https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
disabled={true}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.office365.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
value='https://login.microsoftonline.com/common/oauth2/v2.0/token'
disabled={true}
/>
</div>
);
}
renderGitLab() {
return (
<div>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.gitlab.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitlab.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
/>
}
value={this.state.id}
onChange={this.handleChange}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.gitlab.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitab.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.gitlab.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex "https://<your-gitlab-url>/api/v3/user"')}
helpText={
<FormattedMessage
id='admin.gitlab.userDescription'
defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.userApiEndpoint}
onChange={this.handleChange}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.gitlab.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex "https://<your-gitlab-url>/oauth/authorize"')}
helpText={
<FormattedMessage
id='admin.gitlab.authDescription'
defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.authEndpoint}
onChange={this.handleChange}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.gitlab.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex "https://<your-gitlab-url>/oauth/token"')}
helpText={
<FormattedMessage
id='admin.gitlab.tokenDescription'
defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
}
value={this.state.tokenEndpoint}
onChange={this.handleChange}
/>
</div>
);
}
renderSettings() {
let contents;
let helpText;
if (this.state.oauthType === Constants.GITLAB_SERVICE) {
contents = this.renderGitLab();
helpText = (
<FormattedHTMLMessage
id='admin.gitlab.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
);
} else if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
contents = this.renderGoogle();
helpText = (
<FormattedHTMLMessage
id='admin.google.EnableHtmlDesc'
defaultMessage='<ol><li><a target="_blank" href="https://accounts.google.com/login">Log in</a> to your Google account.</li><li>Go to <a target="_blank" href="https://console.developers.google.com">https://console.developers.google.com</a>, click <strong>Credentials</strong> in the left hand sidebar and enter "Mattermost - your-company-name" as the <strong>Project Name</strong>, then click <strong>Create</strong>.</li><li>Click the <strong>OAuth consent screen</strong> header and enter "Mattermost" as the <strong>Product name shown to users</strong>, then click <strong>Save</strong>.</li><li>Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.</li><li>Under <strong>Restrictions</strong> and <strong>Authorized redirect URIs</strong> enter <strong>your-mattermost-url/signup/google/complete</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.</li><li>Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.</li><li>Finally, go to <a target="_blank" href="https://console.developers.google.com/apis/api/plus/overview">Google+ API</a> and click <strong>Enable</strong>. This might take a few minutes to propagate through Google`s systems.</li></ol>'
/>
);
} else if (this.state.oauthType === Constants.OFFICE365_SERVICE) {
contents = this.renderOffice365();
helpText = (
<FormattedHTMLMessage
id='admin.office365.EnableHtmlDesc'
defaultMessage='<ol><li><a target="_blank" href="https://login.microsoftonline.com/">Log in</a> to your Microsoft or Office 365 account. Make sure it`s the account on the same <a target="_blank" href="https://msdn.microsoft.com/en-us/library/azure/jj573650.aspx#Anchor_0">tenant</a> that you would like users to log in with.</li><li>Go to <a target="_blank" href="https://apps.dev.microsoft.com">https://apps.dev.microsoft.com</a>, click <strong>Go to app list</strong> > <strong>Add an app</strong> and use "Mattermost - your-company-name" as the <strong>Application Name</strong>.</li><li>Under <strong>Application Secrets</strong>, click <strong>Generate New Password</strong> and paste it to the <strong>Application Secret Password</strong> field below.</li><li>Under <strong>Platforms</strong>, click <strong>Add Platform</strong>, choose <strong>Web</strong> and enter <strong>your-mattermost-url/signup/office365/complete</strong> (example: http://localhost:8065/signup/office365/complete) under <strong>Redirect URIs</strong>. Also uncheck <strong>Allow Implicit Flow</strong>.</li><li>Finally, click <strong>Save</strong> and then paste the <strong>Application ID</strong> below.</li></ol>'
/>
);
}
const oauthTypes = [];
oauthTypes.push({value: 'off', text: Utils.localizeMessage('admin.oauth.off', 'Do not allow sign-in via an OAuth 2.0 provider.')});
oauthTypes.push({value: Constants.GITLAB_SERVICE, text: Utils.localizeMessage('admin.oauth.gitlab', 'GitLab')});
if (global.window.mm_license.IsLicensed === 'true') {
if (global.window.mm_license.GoogleOAuth === 'true') {
oauthTypes.push({value: Constants.GOOGLE_SERVICE, text: Utils.localizeMessage('admin.oauth.google', 'Google Apps')});
}
if (global.window.mm_license.Office365OAuth === 'true') {
oauthTypes.push({value: Constants.OFFICE365_SERVICE, text: Utils.localizeMessage('admin.oauth.office365', 'Office 365 (Beta)')});
}
}
return (
<SettingsGroup>
<DropdownSetting
id='oauthType'
values={oauthTypes}
label={
<FormattedMessage
id='admin.oauth.select'
defaultMessage='Select OAuth 2.0 Service Provider:'
/>
}
helpText={helpText}
value={this.state.oauthType}
onChange={this.changeType}
/>
{contents}
</SettingsGroup>
);
}
}

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

@@ -1,281 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import Setting from './setting.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
export default class PasswordSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.getSampleErrorMsg = this.getSampleErrorMsg.bind(this);
this.handlePasswordLengthChange = this.handlePasswordLengthChange.bind(this);
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
this.state = Object.assign(this.state, {
passwordMinimumLength: props.config.PasswordSettings.MinimumLength,
passwordLowercase: props.config.PasswordSettings.Lowercase,
passwordNumber: props.config.PasswordSettings.Number,
passwordUppercase: props.config.PasswordSettings.Uppercase,
passwordSymbol: props.config.PasswordSettings.Symbol,
maximumLoginAttempts: props.config.ServiceSettings.MaximumLoginAttempts
});
// Update sample message from config settings
this.sampleErrorMsg = null;
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
let sampleErrorMsgId = 'user.settings.security.passwordError';
if (props.config.PasswordSettings.Lowercase) {
sampleErrorMsgId += 'Lowercase';
}
if (props.config.PasswordSettings.Uppercase) {
sampleErrorMsgId += 'Uppercase';
}
if (props.config.PasswordSettings.Number) {
sampleErrorMsgId += 'Number';
}
if (props.config.PasswordSettings.Symbol) {
sampleErrorMsgId += 'Symbol';
}
this.sampleErrorMsg = (
<FormattedMessage
id={sampleErrorMsgId}
default='Your password must contain between {min} and {max} characters.'
values={{
min: (this.state.passwordMinimumLength || Constants.MIN_PASSWORD_LENGTH),
max: Constants.MAX_PASSWORD_LENGTH
}}
/>
);
}
}
getConfigFromState(config) {
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
config.PasswordSettings.MinimumLength = this.parseIntNonZero(this.state.passwordMinimumLength, Constants.MIN_PASSWORD_LENGTH);
config.PasswordSettings.Lowercase = this.refs.lowercase.checked;
config.PasswordSettings.Uppercase = this.refs.uppercase.checked;
config.PasswordSettings.Number = this.refs.number.checked;
config.PasswordSettings.Symbol = this.refs.symbol.checked;
}
config.ServiceSettings.MaximumLoginAttempts = this.parseIntNonZero(this.state.maximumLoginAttempts);
return config;
}
getStateFromConfig(config) {
return {
passwordMinimumLength: config.PasswordSettings.MinimumLength,
passwordLowercase: config.PasswordSettings.Lowercase,
passwordNumber: config.PasswordSettings.Number,
passwordUppercase: config.PasswordSettings.Uppercase,
passwordSymbol: config.PasswordSettings.Symbol,
maximumLoginAttempts: config.ServiceSettings.MaximumLoginAttempts
};
}
getSampleErrorMsg(minLength) {
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
if (this.props.config.PasswordSettings.MinimumLength > Constants.MAX_PASSWORD_LENGTH || this.props.config.PasswordSettings.MinimumLength < Constants.MIN_PASSWORD_LENGTH) {
return (
<FormattedMessage
id='user.settings.security.passwordMinLength'
default='Invalid minimum length, cannot show preview.'
/>
);
}
let sampleErrorMsgId = 'user.settings.security.passwordError';
if (this.refs.lowercase.checked) {
sampleErrorMsgId += 'Lowercase';
}
if (this.refs.uppercase.checked) {
sampleErrorMsgId += 'Uppercase';
}
if (this.refs.number.checked) {
sampleErrorMsgId += 'Number';
}
if (this.refs.symbol.checked) {
sampleErrorMsgId += 'Symbol';
}
return (
<FormattedMessage
id={sampleErrorMsgId}
default='Your password must contain between {min} and {max} characters.'
values={{
min: (minLength || Constants.MIN_PASSWORD_LENGTH),
max: Constants.MAX_PASSWORD_LENGTH
}}
/>
);
}
return null;
}
handlePasswordLengthChange(id, value) {
this.sampleErrorMsg = this.getSampleErrorMsg(value);
this.handleChange(id, value);
}
handleCheckboxChange(id, value) {
this.sampleErrorMsg = this.getSampleErrorMsg(this.state.passwordMinimumLength);
this.handleChange(id, value);
}
renderTitle() {
return (
<FormattedMessage
id='admin.security.password'
defaultMessage='Password'
/>
);
}
renderSettings() {
let passwordSettings = null;
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
passwordSettings = (
<div>
<TextSetting
id='passwordMinimumLength'
label={
<FormattedMessage
id='admin.password.minimumLength'
defaultMessage='Minimum Password Length:'
/>
}
placeholder={Utils.localizeMessage('admin.password.minimumLengthExample', 'Ex "5"')}
helpText={
<FormattedMessage
id='admin.password.minimumLengthDescription'
defaultMessage='Minimum number of characters required for a valid password. Must be a whole number greater than or equal to {min} and less than or equal to {max}.'
values={{
min: Constants.MIN_PASSWORD_LENGTH,
max: Constants.MAX_PASSWORD_LENGTH
}}
/>
}
value={this.state.passwordMinimumLength}
onChange={this.handlePasswordLengthChange}
/>
<Setting
label={
<FormattedMessage
id='passwordRequirements'
defaultMessage='Password Requirements:'
/>
}
>
<div>
<label className='checkbox-inline'>
<input
type='checkbox'
ref='lowercase'
defaultChecked={this.state.passwordLowercase}
name='admin.password.lowercase'
onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.lowercase'
defaultMessage='At least one lowercase letter'
/>
</label>
</div>
<div>
<label className='checkbox-inline'>
<input
type='checkbox'
ref='uppercase'
defaultChecked={this.state.passwordUppercase}
name='admin.password.uppercase'
onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.uppercase'
defaultMessage='At least one uppercase letter'
/>
</label>
</div>
<div>
<label className='checkbox-inline'>
<input
type='checkbox'
ref='number'
defaultChecked={this.state.passwordNumber}
name='admin.password.number'
onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.number'
defaultMessage='At least one number'
/>
</label>
</div>
<div>
<label className='checkbox-inline'>
<input
type='checkbox'
ref='symbol'
defaultChecked={this.state.passwordSymbol}
name='admin.password.symbol'
onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.symbol'
defaultMessage='At least one symbol (e.g. "~!@#$%^&*()")'
/>
</label>
</div>
<div>
<br/>
<label>
<FormattedMessage
id='admin.password.preview'
defaultMessage='Error message preview:'
/>
</label>
<br/>
{this.sampleErrorMsg}
</div>
</Setting>
</div>
);
}
return (
<SettingsGroup>
{passwordSettings}
<TextSetting
id='maximumLoginAttempts'
label={
<FormattedMessage
id='admin.service.attemptTitle'
defaultMessage='Maximum Login Attempts:'
/>
}
placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.service.attemptDescription'
defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.'
/>
}
value={this.state.maximumLoginAttempts}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {uploadPlugin, removePlugin, getPlugins} from 'mattermost-redux/actions/admin';
import PluginSettings from './plugin_settings.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
plugins: state.entities.admin.plugins
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
uploadPlugin,
removePlugin,
getPlugins
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PluginSettings);

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

@@ -1,293 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from 'components/loading_screen.jsx';
import Banner from 'components/admin_console/banner.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
export default class PluginSettings extends React.Component {
static propTypes = {
/*
* The config
*/
config: PropTypes.object.isRequired,
/*
* Plugins object with ids as keys and manifests as values
*/
plugins: PropTypes.object.isRequired,
actions: PropTypes.shape({
/*
* Function to upload a plugin
*/
uploadPlugin: PropTypes.func.isRequired,
/*
* Function to remove a plugin
*/
removePlugin: PropTypes.func.isRequired,
/*
* Function to get installed plugins
*/
getPlugins: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.state = {
loading: true,
fileSelected: false,
fileName: null,
serverError: null
};
}
componentDidMount() {
this.props.actions.getPlugins().then(
() => this.setState({loading: false})
);
}
handleChange = () => {
const element = this.refs.fileInput;
if (element.files.length > 0) {
this.setState({fileSelected: true, fileName: element.files[0].name});
}
}
handleSubmit = async (e) => {
e.preventDefault();
const element = this.refs.fileInput;
if (element.files.length === 0) {
return;
}
const file = element.files[0];
this.setState({uploading: true});
const {error} = await this.props.actions.uploadPlugin(file);
this.setState({fileSelected: false, fileName: null, uploading: false});
Utils.clearFileInput(element);
if (error) {
if (error.server_error_id === 'app.plugin.activate.app_error') {
this.setState({serverError: Utils.localizeMessage('admin.plugin.error.activate', 'Unable to upload the plugin. It may conflict with another plugin on your server.')});
} else if (error.server_error_id === 'app.plugin.extract.app_error') {
this.setState({serverError: Utils.localizeMessage('admin.plugin.error.extract', 'Encountered an error when extracting the plugin. Review your plugin file content and try again.')});
} else {
this.setState({serverError: error.message});
}
}
}
handleRemove = async (pluginId) => {
this.setState({removing: pluginId});
const {error} = await this.props.actions.removePlugin(pluginId);
this.setState({removing: null});
if (error) {
this.setState({serverError: error.message});
}
}
render() {
let serverError = '';
if (this.state.serverError) {
serverError = <div className='col-sm-12'><div className='form-group has-error half'><label className='control-label'>{this.state.serverError}</label></div></div>;
}
let btnClass = 'btn';
if (this.state.fileSelected) {
btnClass = 'btn btn-primary';
}
let fileName;
if (this.state.fileName) {
fileName = this.state.fileName;
}
let uploadButtonText;
if (this.state.uploading) {
uploadButtonText = (
<FormattedMessage
id='admin.plugin.uploading'
defaultMessage='Uploading...'
/>
);
} else {
uploadButtonText = (
<FormattedMessage
id='admin.plugin.upload'
defaultMessage='Upload'
/>
);
}
let activePluginsList;
let activePluginsContainer;
const plugins = Object.values(this.props.plugins);
if (this.state.loading) {
activePluginsList = <LoadingScreen/>;
} else if (plugins.length === 0) {
activePluginsContainer = (
<FormattedMessage
id='admin.plugin.no_plugins'
defaultMessage='No active plugins.'
/>
);
} else {
activePluginsList = plugins.map(
(p) => {
let removeButtonText;
if (this.state.removing === p.id) {
removeButtonText = (
<FormattedMessage
id='admin.plugin.removing'
defaultMessage='Removing...'
/>
);
} else {
removeButtonText = (
<FormattedMessage
id='admin.plugin.remove'
defaultMessage='Remove'
/>
);
}
return (
<div key={p.id}>
<div>
<strong>
<FormattedMessage
id='admin.plugin.id'
defaultMessage='ID:'
/>
</strong>
{' ' + p.id}
</div>
<div className='padding-top'>
<strong>
<FormattedMessage
id='admin.plugin.desc'
defaultMessage='Description:'
/>
</strong>
{' ' + p.description}
</div>
<div className='padding-top'>
<a
disabled={this.state.removing === p.id}
onClick={() => this.handleRemove(p.id)}
>
{removeButtonText}
</a>
</div>
<hr/>
</div>
);
}
);
activePluginsContainer = (
<div className='alert alert-transparent'>
{activePluginsList}
</div>
);
}
return (
<div className='wrapper--fixed'>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.plugin.title'
defaultMessage='Plugins (experimental)'
/>
</h3>
<Banner
title={<div/>}
description={
<FormattedHTMLMessage
id='admin.plugin.banner'
defaultMessage='Plugins are experimental stage and are not yet recommended for use in production environments. <br/><br/> Webapp plugins will require users to refresh their browsers or desktop apps before the plugin will take effect. Similarly when a plugin is removed, users will continue to see the plugin until they refresh their browser or app.'
/>
}
/>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.plugin.uploadTitle'
defaultMessage='Upload Plugin: '
/>
</label>
<div className='col-sm-8'>
<div className='file__upload'>
<button className='btn btn-primary'>
<FormattedMessage
id='admin.plugin.choose'
defaultMessage='Choose File'
/>
</button>
<input
ref='fileInput'
type='file'
accept='.gz'
onChange={this.handleChange}
/>
</div>
<button
className={btnClass}
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
>
{uploadButtonText}
</button>
<div className='help-text no-margin'>
{fileName}
</div>
{serverError}
<p className='help-text'>
<FormattedHTMLMessage
id='admin.plugin.uploadDesc'
defaultMessage='Upload a plugin for your Mattermost server. Adding or removing a webapp plugin requires users to refresh their browser or Desktop App before taking effect. See <a href="https://about.mattermost.com/default-plugins">documentation</a> to learn more.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.plugin.activeTitle'
defaultMessage='Active Plugins: '
/>
</label>
<div className='col-sm-8 padding-top'>
{activePluginsContainer}
</div>
</div>
</form>
</div>
);
}
}

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

@@ -1,413 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import SettingsGroup from './settings_group.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import RadioSetting from './radio_setting.jsx';
import PostEditSetting from './post_edit_setting.jsx';
import BooleanSetting from './boolean_setting.jsx';
import TextSetting from './text_setting.jsx';
import ColorSetting from './color_setting.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
export default class PolicySettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.ServiceSettings.RestrictPostDelete = this.state.restrictPostDelete;
config.ServiceSettings.AllowEditPost = this.state.allowEditPost;
config.ServiceSettings.PostEditTimeLimit = this.parseIntNonZero(this.state.postEditTimeLimit, Constants.DEFAULT_POST_EDIT_TIME_LIMIT);
config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite;
config.TeamSettings.RestrictPublicChannelCreation = this.state.restrictPublicChannelCreation;
config.TeamSettings.RestrictPrivateChannelCreation = this.state.restrictPrivateChannelCreation;
config.TeamSettings.RestrictPublicChannelManagement = this.state.restrictPublicChannelManagement;
config.TeamSettings.RestrictPrivateChannelManagement = this.state.restrictPrivateChannelManagement;
config.TeamSettings.RestrictPublicChannelDeletion = this.state.restrictPublicChannelDeletion;
config.TeamSettings.RestrictPrivateChannelDeletion = this.state.restrictPrivateChannelDeletion;
config.TeamSettings.RestrictPrivateChannelManageMembers = this.state.restrictPrivateChannelManageMembers;
config.AnnouncementSettings.EnableBanner = this.state.enableBanner;
config.AnnouncementSettings.BannerText = this.state.bannerText;
config.AnnouncementSettings.BannerColor = this.state.bannerColor;
config.AnnouncementSettings.BannerTextColor = this.state.bannerTextColor;
config.AnnouncementSettings.AllowBannerDismissal = this.state.allowBannerDismissal;
return config;
}
getStateFromConfig(config) {
return {
restrictPostDelete: config.ServiceSettings.RestrictPostDelete,
allowEditPost: config.ServiceSettings.AllowEditPost,
postEditTimeLimit: config.ServiceSettings.PostEditTimeLimit,
restrictTeamInvite: config.TeamSettings.RestrictTeamInvite,
restrictPublicChannelCreation: config.TeamSettings.RestrictPublicChannelCreation,
restrictPrivateChannelCreation: config.TeamSettings.RestrictPrivateChannelCreation,
restrictPublicChannelManagement: config.TeamSettings.RestrictPublicChannelManagement,
restrictPrivateChannelManagement: config.TeamSettings.RestrictPrivateChannelManagement,
restrictPublicChannelDeletion: config.TeamSettings.RestrictPublicChannelDeletion,
restrictPrivateChannelDeletion: config.TeamSettings.RestrictPrivateChannelDeletion,
restrictPrivateChannelManageMembers: config.TeamSettings.RestrictPrivateChannelManageMembers,
enableBanner: config.AnnouncementSettings.EnableBanner,
bannerText: config.AnnouncementSettings.BannerText,
bannerColor: config.AnnouncementSettings.BannerColor,
bannerTextColor: config.AnnouncementSettings.BannerTextColor,
allowBannerDismissal: config.AnnouncementSettings.AllowBannerDismissal
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.general.policy'
defaultMessage='Policy'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<DropdownSetting
id='restrictTeamInvite'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.teamInviteTitle'
defaultMessage='Enable sending team invites from:'
/>
}
value={this.state.restrictTeamInvite}
onChange={this.handleChange}
helpText={
<FormattedHTMLMessage
id='admin.general.policy.teamInviteDescription'
defaultMessage='Set policy on who can invite others to a team using <b>Send Email Invite</b> to invite new users by email, or the <b>Get Team Invite Link</b> and <b>Add Members to Team</b> options from the Main Menu. If <b>Get Team Invite Link</b> is used to share a link, you can expire the invite code from <b>Team Settings</b> > <b>Invite Code</b> after the desired users join the team.'
/>
}
/>
<DropdownSetting
id='restrictPublicChannelCreation'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelCreationTitle'
defaultMessage='Enable public channel creation for:'
/>
}
value={this.state.restrictPublicChannelCreation}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelCreationDescription'
defaultMessage='Set policy on who can create public channels.'
/>
}
/>
<DropdownSetting
id='restrictPublicChannelManagement'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
{value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelManagementTitle'
defaultMessage='Enable public channel renaming for:'
/>
}
value={this.state.restrictPublicChannelManagement}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelManagementDescription'
defaultMessage='Set policy on who can rename and set the header or purpose for public channels.'
/>
}
/>
<DropdownSetting
id='restrictPublicChannelDeletion'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
{value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelDeletionTitle'
defaultMessage='Enable public channel deletion for:'
/>
}
value={this.state.restrictPublicChannelDeletion}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPublicChannelDeletionDescription'
defaultMessage='Set policy on who can delete public channels. Deleted channels can be recovered from the database using a {commandLineToolLink}.'
values={{
commandLineToolLink: (
<a
href='https://docs.mattermost.com/administration/command-line-tools.html'
target='_blank'
rel='noopener noreferrer'
>
<FormattedMessage
id='admin.general.policy.restrictPublicChannelDeletionCommandLineToolLink'
defaultMessage='command line tool'
/>
</a>
)
}}
/>
}
/>
<DropdownSetting
id='restrictPrivateChannelCreation'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelCreationTitle'
defaultMessage='Enable private channel creation for:'
/>
}
value={this.state.restrictPrivateChannelCreation}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelCreationDescription'
defaultMessage='Set policy on who can create private channels.'
/>
}
/>
<DropdownSetting
id='restrictPrivateChannelManagement'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
{value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelManagementTitle'
defaultMessage='Enable private channel renaming for:'
/>
}
value={this.state.restrictPrivateChannelManagement}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelManagementDescription'
defaultMessage='Set policy on who can rename and set the header or purpose for private channels.'
/>
}
/>
<DropdownSetting
id='restrictPrivateChannelManageMembers'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
{value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelManageMembersTitle'
defaultMessage='Enable managing of private group members for:'
/>
}
value={this.state.restrictPrivateChannelManageMembers}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelManageMembersDescription'
defaultMessage='Set policy on who can add and remove members from private groups.'
/>
}
/>
<DropdownSetting
id='restrictPrivateChannelDeletion'
values={[
{value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
{value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
{value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
{value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelDeletionTitle'
defaultMessage='Enable private channel deletion for:'
/>
}
value={this.state.restrictPrivateChannelDeletion}
onChange={this.handleChange}
helpText={
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelDeletionDescription'
defaultMessage='Set policy on who can delete private channels. Deleted channels can be recovered from the database using a {commandLineToolLink}.'
values={{
commandLineToolLink: (
<a
href='https://docs.mattermost.com/administration/command-line-tools.html'
target='_blank'
rel='noopener noreferrer'
>
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink'
defaultMessage='command line tool'
/>
</a>
)
}}
/>
}
/>
<RadioSetting
id='restrictPostDelete'
values={[
{value: Constants.PERMISSIONS_DELETE_POST_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAll', 'Message authors can delete their own messages, and Administrators can delete any message')},
{value: Constants.PERMISSIONS_DELETE_POST_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAdmin', 'Team Admins and System Admins')},
{value: Constants.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostSystemAdmin', 'System Admins')}
]}
label={
<FormattedMessage
id='admin.general.policy.restrictPostDeleteTitle'
defaultMessage='Allow which users to delete messages:'
/>
}
value={this.state.restrictPostDelete}
onChange={this.handleChange}
helpText={
<FormattedHTMLMessage
id='admin.general.policy.restrictPostDeleteDescription'
defaultMessage='Set policy on who has permission to delete messages.'
/>
}
/>
<PostEditSetting
id='allowEditPost'
timeLimitId='postEditTimeLimit'
label={
<FormattedMessage
id='admin.general.policy.allowEditPostTitle'
defaultMessage='Allow users to edit their messages:'
/>
}
value={this.state.allowEditPost}
timeLimitValue={this.state.postEditTimeLimit}
onChange={this.handleChange}
helpText={
<FormattedHTMLMessage
id='admin.general.policy.allowEditPostDescription'
defaultMessage='Set policy on the length of time authors have to edit their messages after posting.'
/>
}
/>
<BooleanSetting
id='enableBanner'
label={
<FormattedMessage
id='admin.general.policy.enableBannerTitle'
defaultMessage='Enable Announcement Banner:'
/>
}
helpText={
<FormattedMessage
id='admin.general.policy.enableBannerDesc'
defaultMessage='Enable an announcement banner across all teams.'
/>
}
value={this.state.enableBanner}
onChange={this.handleChange}
/>
<TextSetting
id='bannerText'
label={
<FormattedMessage
id='admin.general.policy.bannerTextTitle'
defaultMessage='Banner Text:'
/>
}
helpText={
<FormattedMessage
id='admin.general.policy.bannerTextDesc'
defaultMessage='Text that will appear in the announcement banner.'
/>
}
value={this.state.bannerText}
onChange={this.handleChange}
disabled={!this.state.enableBanner}
/>
<ColorSetting
id='bannerColor'
label={
<FormattedMessage
id='admin.general.policy.bannerColorTitle'
defaultMessage='Banner Color:'
/>
}
value={this.state.bannerColor}
onChange={this.handleChange}
disabled={!this.state.enableBanner}
/>
<ColorSetting
id='bannerTextColor'
label={
<FormattedMessage
id='admin.general.policy.bannerTextColorTitle'
defaultMessage='Banner Text Color:'
/>
}
value={this.state.bannerTextColor}
onChange={this.handleChange}
disabled={!this.state.enableBanner}
/>
<BooleanSetting
id='allowBannerDismissal'
label={
<FormattedMessage
id='admin.general.policy.allowBannerDismissalTitle'
defaultMessage='Allow Banner Dismissal:'
/>
}
helpText={
<FormattedMessage
id='admin.general.policy.allowBannerDismissalDesc'
defaultMessage='When true, users can dismiss the banner until its next update. When false, the banner is permanently visible until it is turned off by the System Admin.'
/>
}
value={this.state.allowBannerDismissal}
onChange={this.handleChange}
disabled={!this.state.enableBanner}
/>
</SettingsGroup>
);
}
}

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

@@ -1,101 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Setting from './setting.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
export default class PostEditSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleTimeLimitChange = this.handleTimeLimitChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value);
}
handleTimeLimitChange(e) {
this.props.onChange(this.props.timeLimitId, e.target.value);
}
render() {
return (
<Setting
label={this.props.label}
inputId={this.props.id}
helpText={this.props.helpText}
>
<div className='radio'>
<label>
<input
type='radio'
value={Constants.ALLOW_EDIT_POST_ALWAYS}
name={this.props.id}
checked={this.props.value === Constants.ALLOW_EDIT_POST_ALWAYS}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{Utils.localizeMessage('admin.general.policy.allowEditPostAlways', 'Any time')}
</label>
</div>
<div className='radio'>
<label>
<input
type='radio'
value={Constants.ALLOW_EDIT_POST_NEVER}
name={this.props.id}
checked={this.props.value === Constants.ALLOW_EDIT_POST_NEVER}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{Utils.localizeMessage('admin.general.policy.allowEditPostNever', 'Never')}
</label>
</div>
<div className='radio form-inline'>
<label>
<input
type='radio'
value={Constants.ALLOW_EDIT_POST_TIME_LIMIT}
name={this.props.id}
checked={this.props.value === Constants.ALLOW_EDIT_POST_TIME_LIMIT}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
<input
type='text'
value={this.props.timeLimitValue}
className='form-control'
name={this.props.timeLimitId}
onChange={this.handleTimeLimitChange}
disabled={this.props.disabled || this.props.value !== Constants.ALLOW_EDIT_POST_TIME_LIMIT}
/>
<span> {Utils.localizeMessage('admin.general.policy.allowEditPostTimeLimit', 'seconds after posting')}</span>
</label>
</div>
</Setting>
);
}
}
PostEditSetting.defaultProps = {
isDisabled: false
};
PostEditSetting.propTypes = {
id: PropTypes.string.isRequired,
timeLimitId: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
timeLimitValue: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
helpText: PropTypes.node
};

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

@@ -1,83 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class PrivacySettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress;
config.PrivacySettings.ShowFullName = this.state.showFullName;
return config;
}
getStateFromConfig(config) {
return {
showEmailAddress: config.PrivacySettings.ShowEmailAddress,
showFullName: config.PrivacySettings.ShowFullName
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.general.privacy'
defaultMessage='Privacy'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='showEmailAddress'
label={
<FormattedMessage
id='admin.privacy.showEmailTitle'
defaultMessage='Show Email Address: '
/>
}
helpText={
<FormattedMessage
id='admin.privacy.showEmailDescription'
defaultMessage='When false, hides the email address of members from everyone except System Administrators.'
/>
}
value={this.state.showEmailAddress}
onChange={this.handleChange}
/>
<BooleanSetting
id='showFullName'
label={
<FormattedMessage
id='admin.privacy.showFullNameTitle'
defaultMessage='Show Full Name: '
/>
}
helpText={
<FormattedMessage
id='admin.privacy.showFullNameDescription'
defaultMessage='When false, hides the full name of members from everyone except System Administrators. Username is shown in place of full name.'
/>
}
value={this.state.showFullName}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,84 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
export default class PublicLinkSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.FileSettings.EnablePublicLink = this.state.enablePublicLink;
config.FileSettings.PublicLinkSalt = this.state.publicLinkSalt;
return config;
}
getStateFromConfig(config) {
return {
enablePublicLink: config.FileSettings.EnablePublicLink,
publicLinkSalt: config.FileSettings.PublicLinkSalt
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.security.public_links'
defaultMessage='Public Links'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enablePublicLink'
label={
<FormattedMessage
id='admin.image.shareTitle'
defaultMessage='Enable Public File Links: '
/>
}
helpText={
<FormattedMessage
id='admin.image.shareDescription'
defaultMessage='Allow users to share public links to files and images.'
/>
}
value={this.state.enablePublicLink}
onChange={this.handleChange}
/>
<GeneratedSetting
id='publicLinkSalt'
label={
<FormattedMessage
id='admin.image.publicLinkTitle'
defaultMessage='Public Link Salt:'
/>
}
helpText={
<FormattedMessage
id='admin.image.publicLinkDescription'
defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Regenerate" to create new salt.'
/>
}
value={this.state.publicLinkSalt}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,233 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
const PUSH_NOTIFICATIONS_OFF = 'off';
const PUSH_NOTIFICATIONS_MHPNS = 'mhpns';
const PUSH_NOTIFICATIONS_MTPNS = 'mtpns';
const PUSH_NOTIFICATIONS_CUSTOM = 'custom';
export default class PushSettings extends AdminSettings {
constructor(props) {
super(props);
this.canSave = this.canSave.bind(this);
this.handleAgreeChange = this.handleAgreeChange.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
canSave() {
return this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_MHPNS || this.state.agree;
}
handleAgreeChange(e) {
this.setState({
agree: e.target.checked
});
}
handleChange(id, value) {
if (id === 'pushNotificationServerType') {
this.setState({
agree: false
});
if (value === PUSH_NOTIFICATIONS_MHPNS) {
this.setState({
pushNotificationServer: Constants.MHPNS
});
} else if (value === PUSH_NOTIFICATIONS_MTPNS) {
this.setState({
pushNotificationServer: Constants.MTPNS
});
}
}
super.handleChange(id, value);
}
getConfigFromState(config) {
config.EmailSettings.SendPushNotifications = this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_OFF;
config.EmailSettings.PushNotificationServer = this.state.pushNotificationServer.trim();
config.EmailSettings.PushNotificationContents = this.state.pushNotificationContents;
return config;
}
getStateFromConfig(config) {
let pushNotificationServerType = PUSH_NOTIFICATIONS_CUSTOM;
let agree = false;
if (!config.EmailSettings.SendPushNotifications) {
pushNotificationServerType = PUSH_NOTIFICATIONS_OFF;
} else if (config.EmailSettings.PushNotificationServer === Constants.MHPNS &&
global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
pushNotificationServerType = PUSH_NOTIFICATIONS_MHPNS;
agree = true;
} else if (config.EmailSettings.PushNotificationServer === Constants.MTPNS) {
pushNotificationServerType = PUSH_NOTIFICATIONS_MTPNS;
}
let pushNotificationServer = config.EmailSettings.PushNotificationServer;
if (pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
pushNotificationServer = Constants.MTPNS;
} else if (pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
pushNotificationServer = Constants.MHPNS;
}
return {
pushNotificationServerType,
pushNotificationServer,
pushNotificationContents: config.EmailSettings.PushNotificationContents,
agree
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.notifications.title'
defaultMessage='Notification Settings'
/>
);
}
renderSettings() {
const pushNotificationServerTypes = [];
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_OFF, text: Utils.localizeMessage('admin.email.pushOff', 'Do not send push notifications')});
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MHPNS, text: Utils.localizeMessage('admin.email.mhpns', 'Use encrypted, production-quality HPNS connection to iOS and Android apps')});
}
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MTPNS, text: Utils.localizeMessage('admin.email.mtpns', 'Use iOS and Android apps on iTunes and Google Play with TPNS')});
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_CUSTOM, text: Utils.localizeMessage('admin.email.selfPush', 'Manually enter Push Notification Service location')});
let sendHelpText = null;
let pushServerHelpText = null;
if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF) {
sendHelpText = (
<FormattedHTMLMessage
id='admin.email.pushOffHelp'
defaultMessage='Please see <a href="https://about.mattermost.com/default-mobile-push-notifications/" target="_blank">documentation on push notifications</a> to learn more about setup options.'
/>
);
} else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mhpnsHelp'
defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="https://about.mattermost.com/default-hpns/" target="_blank">Mattermost Hosted Push Notification Service</a>.'
/>
);
} else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mtpnsHelp'
defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="https://about.mattermost.com/default-tpns/" target="_blank">Mattermost Test Push Notification Service</a>.'
/>
);
} else {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.easHelp'
defaultMessage='Learn more about compiling and deploying your own mobile apps from an <a href="https://about.mattermost.com/default-enterprise-app-store" target="_blank">Enterprise App Store</a>.'
/>
);
}
let tosCheckbox;
if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
tosCheckbox = (
<div className='form-group'>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<input
type='checkbox'
ref='agree'
checked={this.state.agree}
onChange={this.handleAgreeChange}
/>
<FormattedHTMLMessage
id='admin.email.agreeHPNS'
defaultMessage=' I understand and accept the Mattermost Hosted Push Notification Service <a href="https://about.mattermost.com/hpns-terms/" target="_blank">Terms of Service</a> and <a href="https://about.mattermost.com/hpns-privacy/" target="_blank">Privacy Policy</a>.'
/>
</div>
</div>
);
}
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.notifications.push'
defaultMessage='Mobile Push'
/>
}
>
<DropdownSetting
id='pushNotificationServerType'
values={pushNotificationServerTypes}
label={
<FormattedMessage
id='admin.email.pushTitle'
defaultMessage='Enable Push Notifications: '
/>
}
value={this.state.pushNotificationServerType}
onChange={this.handleChange}
helpText={sendHelpText}
/>
{tosCheckbox}
<TextSetting
id='pushNotificationServer'
label={
<FormattedMessage
id='admin.email.pushServerTitle'
defaultMessage='Push Notification Server:'
/>
}
placeholder={Utils.localizeMessage('admin.email.pushServerEx', 'E.g.: "http://push-test.mattermost.com"')}
helpText={pushServerHelpText}
value={this.state.pushNotificationServer}
onChange={this.handleChange}
disabled={this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_CUSTOM}
/>
<DropdownSetting
id='pushNotificationContents'
values={[
{value: 'generic_no_channel', text: Utils.localizeMessage('admin.email.genericNoChannelPushNotification', '"Send generic description with only sender name')},
{value: 'generic', text: Utils.localizeMessage('admin.email.genericPushNotification', 'Send generic description with sender and channel names')},
{value: 'full', text: Utils.localizeMessage('admin.email.fullPushNotification', 'Send full message snippet')}
]}
label={
<FormattedMessage
id='admin.email.pushContentTitle'
defaultMessage='Push Notification Contents:'
/>
}
value={this.state.pushNotificationContents}
onChange={this.handleChange}
disabled={this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF}
helpText={
<FormattedHTMLMessage
id='admin.email.pushContentDesc'
defaultMessage='"Send generic description with only sender name" includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents.<br /><br />"Send generic description with sender and channel names" includes the name of the person who sent the message and the channel it was sent in, but not the message text.<br /><br />"Send full message snippet" includes a message excerpt in push notifications, which may contain confidential information sent in messages. If your Push Notification Service is outside your firewall, it is *highly recommended* this option only be used with an "https" protocol to encrypt the connection.'
/>
}
/>
</SettingsGroup>
);
}
}

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

@@ -1,65 +0,0 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Setting from './setting.jsx';
export default class RadioSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value);
}
render() {
const options = [];
for (const {value, text} of this.props.values) {
options.push(
<div className='radio'>
<label>
<input
type='radio'
value={value}
name={this.props.id}
checked={value === this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{text}
</label>
</div>
);
}
return (
<Setting
label={this.props.label}
inputId={this.props.id}
helpText={this.props.helpText}
>
{options}
</Setting>
);
}
}
RadioSetting.defaultProps = {
isDisabled: false
};
RadioSetting.propTypes = {
id: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
helpText: PropTypes.node
};

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

@@ -1,179 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class RateSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
getConfigFromState(config) {
config.RateLimitSettings.Enable = this.state.enable;
config.RateLimitSettings.PerSec = this.parseIntNonZero(this.state.perSec);
config.RateLimitSettings.MaxBurst = this.parseIntNonZero(this.state.maxBurst);
config.RateLimitSettings.MemoryStoreSize = this.parseIntNonZero(this.state.memoryStoreSize);
config.RateLimitSettings.VaryByRemoteAddr = this.state.varyByRemoteAddr;
config.RateLimitSettings.VaryByHeader = this.state.varyByHeader;
return config;
}
getStateFromConfig(config) {
return {
enable: config.RateLimitSettings.Enable,
perSec: config.RateLimitSettings.PerSec,
maxBurst: config.RateLimitSettings.MaxBurst,
memoryStoreSize: config.RateLimitSettings.MemoryStoreSize,
varyByRemoteAddr: config.RateLimitSettings.VaryByRemoteAddr,
varyByHeader: config.RateLimitSettings.VaryByHeader
};
}
renderTitle() {
return (
<FormattedMessage
id='admin.rate.title'
defaultMessage='Rate Limit Settings'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<FormattedMessage
id='admin.rate.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</div>
</div>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.rate.enableLimiterTitle'
defaultMessage='Enable Rate Limiting: '
/>
}
helpText={
<FormattedMessage
id='admin.rate.enableLimiterDescription'
defaultMessage='When true, APIs are throttled at rates specified below.'
/>
}
value={this.state.enable}
onChange={this.handleChange}
/>
<TextSetting
id='perSec'
label={
<FormattedMessage
id='admin.rate.queriesTitle'
defaultMessage='Maximum Queries per Second:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.rate.queriesDescription'
defaultMessage='Throttles API at this number of requests per second.'
/>
}
value={this.state.perSec}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='maxBurst'
label={
<FormattedMessage
id='admin.rate.maxBurst'
defaultMessage='Maximum Burst Size:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.maxBurstExample', 'Ex "100"')}
helpText={
<FormattedMessage
id='admin.rate.maxBurstDescription'
defaultMessage='Maximum number of requests allowed beyond the per second query limit.'
/>
}
value={this.state.maxBurst}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='memoryStoreSize'
label={
<FormattedMessage
id='admin.rate.memoryTitle'
defaultMessage='Memory Store Size:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')}
helpText={
<FormattedMessage
id='admin.rate.memoryDescription'
defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary rate limit by remote address" and "Vary rate limit by HTTP header".'
/>
}
value={this.state.memoryStoreSize}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<BooleanSetting
id='varyByRemoteAddr'
label={
<FormattedMessage
id='admin.rate.remoteTitle'
defaultMessage='Vary rate limit by remote address: '
/>
}
helpText={
<FormattedMessage
id='admin.rate.remoteDescription'
defaultMessage='When true, rate limit API access by IP address.'
/>
}
value={this.state.varyByRemoteAddr}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='varyByHeader'
label={
<FormattedMessage
id='admin.rate.httpHeaderTitle'
defaultMessage='Vary rate limit by HTTP header:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')}
helpText={
<FormattedMessage
id='admin.rate.httpHeaderDescription'
defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
/>
}
value={this.state.varyByHeader}
onChange={this.handleChange}
disabled={!this.state.enable || this.state.varyByRemoteAddr}
/>
</SettingsGroup>
);
}
}

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

@@ -1,62 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import Setting from './setting.jsx';
export default class RemoveFileSetting extends Setting {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
helpText: PropTypes.node,
removeButtonText: PropTypes.node.isRequired,
removingText: PropTypes.node,
fileName: PropTypes.string.isRequired,
onSubmit: PropTypes.func.isRequired,
disabled: PropTypes.bool
};
}
constructor(props) {
super(props);
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove(e) {
e.preventDefault();
$(this.refs.remove_button).button('loading');
this.props.onSubmit(this.props.id, () => {
$(this.refs.remove_button).button('reset');
});
}
render() {
return (
<Setting
label={this.props.label}
helpText={this.props.helpText}
inputId={this.props.id}
>
<div>
<div className='help-text remove-filename'>
{this.props.fileName}
</div>
<button
className='btn btn-danger'
onClick={this.handleRemove}
ref='remove_button'
disabled={this.props.disabled}
data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.removingText}`}
>
{this.props.removeButtonText}
</button>
</div>
</Setting>
);
}
}

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

@@ -1,262 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import * as Utils from 'utils/utils.jsx';
/**
* A button which, when clicked, performs an action and displays
* its outcome as either success, or failure accompanied by the
* `message` property of the `err` object.
*/
export default class RequestButton extends React.Component {
static propTypes = {
/**
* The action to be called to carry out the request.
*/
requestAction: PropTypes.func.isRequired,
/**
* A component that displays help text for the request button.
*
* Typically, this will be a <FormattedMessage/>.
*/
helpText: PropTypes.element.isRequired,
/**
* A component to be displayed on the button.
*
* Typically, this will be a <FormattedMessage/>
*/
buttonText: PropTypes.element.isRequired,
/**
* The element to display as the field label.
*
* Typically, this will be a <FormattedMessage/>
*/
label: PropTypes.element,
/**
* True if the button form control should be disabled, otherwise false.
*/
disabled: PropTypes.bool,
/**
* True if the config needs to be saved before running the request, otherwise false.
*
* If set to true, the action provided in the `saveConfigAction` property will be
* called before the action provided in the `requestAction` property, with the later
* only being called if the former is successful.
*/
saveNeeded: PropTypes.bool,
/**
* Action to be called to save the config, if saveNeeded is set to true.
*/
saveConfigAction: PropTypes.func,
/**
* True if the success message should be show when the request completes successfully,
* otherwise false.
*/
showSuccessMessage: PropTypes.bool,
/**
* The message to show when the request completes successfully.
*/
successMessage: PropTypes.shape({
/**
* The i18n string ID for the success message.
*/
id: PropTypes.string.isRequired,
/**
* The i18n default value for the success message.
*/
defaultMessage: PropTypes.string.isRequired
}),
/**
* The message to show when the request returns an error.
*/
errorMessage: PropTypes.shape({
/**
* The i18n string ID for the error message.
*/
id: PropTypes.string.isRequired,
/**
* The i18n default value for the error message.
*
* The placeholder {error} may be used to include the error message returned
* by the server in response to the failed request.
*/
defaultMessage: PropTypes.string.isRequired
}),
/**
* True if the {error} placeholder for the `errorMessage` property should include both
* the `message` and `detailed_error` properties of the error returned from the server,
* otherwise false to include only the `message` property.
*/
includeDetailedError: PropTypes.bool,
/**
* An element to display adjacent to the request button.
*/
alternativeActionElement: PropTypes.element
}
static defaultProps = {
disabled: false,
saveNeeded: false,
showSuccessMessage: true,
includeDetailedError: false,
successMessage: {
id: 'admin.requestButton.requestSuccess',
defaultMessage: 'Test Successful'
},
errorMessage: {
id: 'admin.requestButton.requestFailure',
defaultMessage: 'Test Failure: {error}'
}
}
constructor(props) {
super(props);
this.handleRequest = this.handleRequest.bind(this);
this.state = {
busy: false,
fail: null,
success: false
};
}
handleRequest(e) {
e.preventDefault();
this.setState({
busy: true,
fail: null,
success: false
});
const doRequest = () => { //eslint-disable-line func-style
this.props.requestAction(
() => {
this.setState({
busy: false,
success: true
});
},
(err) => {
let errMsg = err.message;
if (this.props.includeDetailedError) {
errMsg += ' - ' + err.detailed_error;
}
this.setState({
busy: false,
fail: errMsg
});
}
);
};
if (this.props.saveNeeded) {
this.props.saveConfigAction(doRequest);
} else {
doRequest();
}
}
render() {
let message = null;
if (this.state.fail) {
message = (
<div>
<div className='alert alert-warning'>
<i className='fa fa-warning'/>
<FormattedMessage
id={this.props.errorMessage.id}
defaultMessage={this.props.errorMessage.defaultMessage}
values={{
error: this.state.fail
}}
/>
</div>
</div>
);
} else if (this.state.success && this.props.showSuccessMessage) {
message = (
<div>
<div className='alert alert-success'>
<i className='fa fa-success'/>
<FormattedMessage
id={this.props.successMessage.id}
defaultMessage={this.props.successMessage.defaultMessage}
/>
</div>
</div>
);
}
let contents = null;
if (this.state.busy) {
contents = (
<span>
<span className='fa fa-refresh icon--rotate'/>
{Utils.localizeMessage('admin.requestButton.loading', ' Loading...')}
</span>
);
} else {
contents = this.props.buttonText;
}
let widgetClassNames = 'col-sm-8';
let label = null;
if (this.props.label) {
label = (
<label
className='control-label col-sm-4'
>
{this.props.label}
</label>
);
} else {
widgetClassNames = 'col-sm-offset-4 ' + widgetClassNames;
}
return (
<div className='form-group'>
{label}
<div className={widgetClassNames}>
<div>
<button
className='btn btn-default'
onClick={this.handleRequest}
disabled={this.props.disabled}
>
{contents}
</button>
{this.props.alternativeActionElement}
{message}
</div>
<div className='help-text'>
{this.props.helpText}
</div>
</div>
</div>
);
}
}

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

@@ -1,165 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {adminResetPassword} from 'actions/admin_actions.jsx';
import PropTypes from 'prop-types';
import React from 'react';
export default class ResetPasswordModal extends React.Component {
static propTypes = {
user: PropTypes.object,
show: PropTypes.bool.isRequired,
onModalSubmit: PropTypes.func,
onModalDismissed: PropTypes.func
};
static defaultProps = {
show: false
};
constructor(props) {
super(props);
this.doSubmit = this.doSubmit.bind(this);
this.doCancel = this.doCancel.bind(this);
this.state = {
serverError: null
};
}
doSubmit(e) {
e.preventDefault();
const password = this.refs.password.value;
const passwordErr = Utils.isValidPassword(password);
if (passwordErr) {
this.setState({serverError: passwordErr});
return;
}
this.setState({serverError: null});
adminResetPassword(
this.props.user.id,
password,
() => {
this.props.onModalSubmit(this.props.user);
},
(err) => {
this.setState({serverError: err.message});
}
);
}
doCancel() {
this.setState({serverError: null});
this.props.onModalDismissed();
}
render() {
const user = this.props.user;
if (user == null) {
return <div/>;
}
let urlClass = 'input-group input-group--limit';
let serverError = null;
if (this.state.serverError) {
urlClass += ' has-error';
serverError = <div className='has-error'><p className='input__help error'>{this.state.serverError}</p></div>;
}
let title;
if (user.auth_service) {
title = (
<FormattedMessage
id='admin.reset_password.titleSwitch'
defaultMessage='Switch Account to Email/Password'
/>
);
} else {
title = (
<FormattedMessage
id='admin.reset_password.titleReset'
defaultMessage='Reset Password'
/>
);
}
return (
<Modal
show={this.props.show}
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
<Modal.Title>
{title}
</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'
>
<FormattedMessage
id='admin.reset_password.newPassword'
defaultMessage='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}
>
<FormattedMessage
id='admin.reset_password.close'
defaultMessage='Close'
/>
</button>
<button
onClick={this.doSubmit}
type='submit'
className='btn btn-primary'
tabIndex='2'
>
<FormattedMessage
id='admin.reset_password.select'
defaultMessage='Select'
/>
</button>
</Modal.Footer>
</form>
</Modal>
);
}
}

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

@@ -1,24 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {revokeUserAccessToken} from 'mattermost-redux/actions/users';
import RevokeTokenButton from './revoke_token_button.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
revokeUserAccessToken
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(RevokeTokenButton);

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше