Removing webapp
Этот коммит содержится в:
@@ -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:'
|
||||
/>
|
||||
{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:'
|
||||
/>
|
||||
{config.BuildHash}
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='about.hashee'
|
||||
defaultMessage='EE Build Hash:'
|
||||
/>
|
||||
{config.BuildHashEnterprise}
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='about.date'
|
||||
defaultMessage='Build Date:'
|
||||
/>
|
||||
{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 user’s 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 user’s 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 you’re 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);
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user