Workspace dashboard improvements (#22883)

Automatic Merge
Этот коммит содержится в:
Colton Shaw
2023-04-14 09:34:28 -07:00
коммит произвёл GitHub
родитель c99bbd0b15
Коммит 3c5fad563e
12 изменённых файлов: 835 добавлений и 588 удалений

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

@@ -6,7 +6,7 @@ import {shallow} from 'enzyme';
import ChipsList, {ChipsInfoType} from 'components/admin_console/workspace-optimization/chips_list';
import {ItemStatus} from './dashboard.data';
import {ItemStatus} from './dashboard.type';
describe('components/admin_console/workspace-optimization/chips_list', () => {
const overallScoreChips: ChipsInfoType = {

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

@@ -6,7 +6,7 @@ import {FormattedMessage} from 'react-intl';
import Chip from 'components/common/chip/chip';
import {ItemStatus} from './dashboard.data';
import {ItemStatus} from './dashboard.type';
import './dashboard.scss';

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {useEffect, useMemo, useState} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
@@ -14,56 +14,23 @@ import {
AccountMultipleOutlineIcon,
} from '@mattermost/compass-icons/components';
import {getLicense} from 'mattermost-redux/selectors/entities/general';
import {getLicense, getServerVersion} from 'mattermost-redux/selectors/entities/general';
import {GlobalState} from '@mattermost/types/store';
import {CloudLinks, ConsolePages, DocLinks} from 'utils/constants';
import {ConsolePages} from 'utils/constants';
import {daysToLicenseExpire, isEnterpriseOrE20License, getIsStarterLicense} from '../../../utils/license_utils';
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
import {AdminConfig} from '@mattermost/types/config';
import {runConfigChecks} from './dashboard_checks/config';
import {DataModel, ItemStatus, Options} from './dashboard.type';
import {runAccessChecks} from './dashboard_checks/access';
import {runDataPrivacyChecks} from './dashboard_checks/data_privacy';
import {runPerformanceChecks} from './dashboard_checks/performance';
import {runEaseOfUseChecks} from './dashboard_checks/easy_management';
import {runUpdateChecks} from './dashboard_checks/updates';
export type DataModel = {
[key: string]: {
title: string;
description: string;
descriptionOk: string;
items: ItemModel[];
icon: React.ReactNode;
hide?: boolean;
};
}
export enum ItemStatus {
NONE = 'none',
OK = 'ok',
INFO = 'info',
WARNING = 'warning',
ERROR = 'error',
}
export type ItemModel = {
id: string;
title: string;
description: string;
status: ItemStatus;
scoreImpact: number;
impactModifier: number;
configUrl?: string;
configText?: string;
telemetryAction?: string;
infoUrl?: string;
infoText?: string;
}
export type UpdatesParam = {
serverVersion: {
type: string;
status: ItemStatus;
description: string;
};
}
const impactModifiers: Record<ItemStatus, number> = {
export const impactModifiers: Record<ItemStatus, number> = {
[ItemStatus.NONE]: 1,
[ItemStatus.OK]: 1,
[ItemStatus.INFO]: 0.5,
@@ -71,11 +38,191 @@ const impactModifiers: Record<ItemStatus, number> = {
[ItemStatus.ERROR]: 0,
};
const useMetricsData = () => {
const getUpdatesData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.title',
defaultMessage: 'Server updates',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.description',
defaultMessage: 'An update is available.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.descriptionOk',
defaultMessage: 'Your workspace is completely up to date!',
}),
icon: (
<div className='icon'>
<ArrowUpBoldCircleOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runUpdateChecks(config, formatMessage, options),
});
const getConfigurationData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.title',
defaultMessage: 'Configuration',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.description',
defaultMessage: 'You have configuration issues to resolve',
}),
hide: options.isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.descriptionOk',
defaultMessage: 'You\'ve successfully configured SSL and Session Lengths!',
}),
icon: (
<div className='icon'>
<TuneIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runConfigChecks(config, formatMessage, options),
});
const getAccessData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.access.title',
defaultMessage: 'Workspace access',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.access.description',
defaultMessage: 'Web server configuration may be affecting access to your Mattermost workspace.',
}),
hide: options.isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.access.descriptionOk',
defaultMessage: 'Your web server configuration is passing a live URL test!',
}),
icon: (
<div className='icon'>
<ServerVariantIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runAccessChecks(config, formatMessage),
});
const getPerformanceData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.title',
defaultMessage: 'Performance',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.description',
defaultMessage: 'Your server would benefit from some performance tweaks.',
}),
hide: options.isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.descriptionOk',
defaultMessage: 'Your search performance suits your workspace usage!',
}),
icon: (
<div className='icon'>
<ChartLineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runPerformanceChecks(config, formatMessage, options),
});
const getDataPrivacyData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.title',
defaultMessage: 'Data privacy',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.description',
defaultMessage: 'Get better insight and control over your data.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.descriptionOk',
defaultMessage: 'You\'ve enabled data retention and compliance features!',
}),
icon: (
<div className='icon'>
<LockOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runDataPrivacyChecks(config, formatMessage, options),
});
const getEaseOfManagementData = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.title',
defaultMessage: 'Ease of management',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.description',
defaultMessage: 'Make it easier to manage your Mattermost workspace.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.descriptionOk',
defaultMessage: 'Your user authentication setup is appropriate based on your current usage!',
}),
icon: (
<div className='icon'>
<AccountMultipleOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: await runEaseOfUseChecks(config, formatMessage, options),
});
const useMetricsData = (
config: Partial<AdminConfig>,
) => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState<DataModel | undefined>(undefined);
const {formatMessage} = useIntl();
const prevTrialLicense = useSelector((state: GlobalState) => state.entities.admin.prevTrialLicense);
const license = useSelector(getLicense);
// get the currently installed server version
const installedVersion = useSelector((state: GlobalState) => getServerVersion(state));
const analytics = useSelector((state: GlobalState) => state.entities.admin.analytics) as unknown as Options['analytics'];
const canStartTrial = license?.IsLicensed !== 'true' && prevTrialLicense?.IsLicensed !== 'true';
const daysUntilExpiration = daysToLicenseExpire(license) || -1;
@@ -87,358 +234,46 @@ const useMetricsData = () => {
const [, contactSalesLink] = useOpenSalesLink();
const trialOrEnterpriseCtaConfig = {
const trialOrEnterpriseCtaConfig = useMemo(() => ({
configUrl: canStartTrial ? ConsolePages.LICENSE : contactSalesLink,
configText: canStartTrial ? formatMessage({id: 'admin.reporting.workspace_optimization.cta.startTrial', defaultMessage: 'Start trial'}) : formatMessage({id: 'admin.reporting.workspace_optimization.cta.upgradeLicense', defaultMessage: 'Contact sales'}),
}), [canStartTrial, contactSalesLink, formatMessage]);
const options: Options = useMemo(() => ({
isLicensed,
isEnterpriseLicense,
trialOrEnterpriseCtaConfig,
isStarterLicense,
isCloud,
analytics,
installedVersion,
}), [isLicensed, isEnterpriseLicense, trialOrEnterpriseCtaConfig, isStarterLicense, isCloud, analytics, installedVersion]);
useEffect(() => {
setLoading(true);
const refreshData = async () => {
const data = {
updates: await getUpdatesData(config, formatMessage, options),
configuration: await getConfigurationData(config, formatMessage, options),
access: await getAccessData(config, formatMessage, options),
performance: await getPerformanceData(config, formatMessage, options),
dataPrivacy: await getDataPrivacyData(config, formatMessage, options),
easyManagement: await getEaseOfManagementData(config, formatMessage, options),
};
return data;
};
refreshData().then((data) => {
setData(data);
setLoading(false);
});
}, [config, formatMessage, options]);
return {
data,
loading,
};
const getUpdatesData = (data: UpdatesParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.title',
defaultMessage: 'Server updates',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.description',
defaultMessage: 'An update is available.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.descriptionOk',
defaultMessage: 'Your workspace is completely up to date!',
}),
icon: (
<div className='icon'>
<ArrowUpBoldCircleOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'server_version',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.status.title',
defaultMessage: '{type} version update available.',
}, {type: data.serverVersion.type}),
description: data.serverVersion.description,
configUrl: CloudLinks.DOWNLOAD_UPDATE,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.downloadUpdate', defaultMessage: 'Download update'}),
infoUrl: DocLinks.UPGRADE_SERVER,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'server-version',
status: data.serverVersion.status,
scoreImpact: 15,
impactModifier: impactModifiers[data.serverVersion.status],
},
],
});
type ConfigurationParam = {
ssl: {
status: ItemStatus;
};
sessionLength: {
status: ItemStatus;
};
}
const getConfigurationData = (data: ConfigurationParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.title',
defaultMessage: 'Configuration',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.description',
defaultMessage: 'You have configuration issues to resolve',
}),
hide: isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.descriptionOk',
defaultMessage: 'You\'ve successfully configured SSL and Session Lengths!',
}),
icon: (
<div className='icon'>
<TuneIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'ssl',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.ssl.title',
defaultMessage: 'Configure SSL to make your server more secure',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.ssl.description',
defaultMessage: 'We strongly recommend securing your Mattermost workspace by configuring SSL in production environments.',
}),
infoUrl: DocLinks.SSL_CERTIFICATE,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'ssl',
status: data.ssl.status,
scoreImpact: 25,
impactModifier: impactModifiers[data.ssl.status],
},
{
id: 'session-length',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.session_length.title',
defaultMessage: 'Session lengths is set to default',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.session_length.description',
defaultMessage: 'Your session length is set to the default of 30 days. A longer session length provides convenience, and a shorter session provides tighter security. We recommend adjusting this based on your organization\'s security policies.',
}),
configUrl: ConsolePages.SESSION_LENGTHS,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.configureSessionLength', defaultMessage: 'Configure session length'}),
infoUrl: DocLinks.SESSION_LENGTHS,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'session-length',
status: data.sessionLength.status,
scoreImpact: 8,
impactModifier: impactModifiers[data.sessionLength.status],
},
],
});
type AccessParam = {
siteUrl: {
status: ItemStatus;
};
}
const getAccessData = (data: AccessParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.access.title',
defaultMessage: 'Workspace access',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.access.description',
defaultMessage: 'Web server configuration may be affecting access to your Mattermost workspace.',
}),
hide: isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.access.descriptionOk',
defaultMessage: 'Your web server configuration is passing a live URL test!',
}),
icon: (
<div className='icon'>
<ServerVariantIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'site-url',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.access.site_url.title',
defaultMessage: 'Misconfigured web server',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.access.site_url.description',
defaultMessage: 'Your web server settings aren\'t passing a live URL test which means your workspace may not be accessible to users. We recommend updating your web server settings.',
}),
configUrl: ConsolePages.WEB_SERVER,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.configureWebServer', defaultMessage: 'Configure web server'}),
infoUrl: DocLinks.SITE_URL,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'site-url',
status: data.siteUrl.status,
scoreImpact: 12,
impactModifier: impactModifiers[data.siteUrl.status],
},
],
});
type PerformanceParam = {
search: {
status: ItemStatus;
};
}
const getPerformanceData = (data: PerformanceParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.title',
defaultMessage: 'Performance',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.description',
defaultMessage: 'Your server would benefit from some performance tweaks.',
}),
hide: isCloud,
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.descriptionOk',
defaultMessage: 'Your search performance suits your workspace usage!',
}),
icon: (
<div className='icon'>
<ChartLineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'search',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.search.title',
defaultMessage: 'Search performance',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.search.description',
defaultMessage: 'Your server has reached over 500 users and 2 million posts which can result in slow search performance. We recommend enabling Elasticsearch for better performance.',
}),
...(isLicensed && isEnterpriseLicense ? {
configUrl: ConsolePages.ELASTICSEARCH,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.configureElasticsearch', defaultMessage: 'Try Elasticsearch'}),
} : trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.ELASTICSEARCH,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'search-optimization',
status: data.search.status,
scoreImpact: 20,
impactModifier: impactModifiers[data.search.status],
},
],
});
type DataPrivacyParam = {
retention: {
status: ItemStatus;
};
}
// TBD
const getDataPrivacyData = (data: DataPrivacyParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.title',
defaultMessage: 'Data privacy',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.description',
defaultMessage: 'Get better insight and control over your data.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.descriptionOk',
defaultMessage: 'You\'ve enabled data retention and compliance features!',
}),
icon: (
<div className='icon'>
<LockOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'data-retention',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.retention.title',
defaultMessage: 'Become more data aware',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.retention.description',
defaultMessage: 'Organizations in highly regulated industries require more control and insight with their data. We recommend enabling Data Retention and Compliance features.',
}),
...(isLicensed && isEnterpriseLicense ? {
configUrl: ConsolePages.DATA_RETENTION,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.configureDataRetention', defaultMessage: 'Try data retention'}),
} : trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.DATA_RETENTION_POLICY,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'data-retention',
status: data.retention.status,
scoreImpact: 16,
impactModifier: impactModifiers[data.retention.status],
},
],
});
type EaseOfManagementParam = {
ldap: {
status: ItemStatus;
};
guestAccounts?: {
status: ItemStatus;
};
}
// TBD
const getEaseOfManagementData = (data: EaseOfManagementParam) => ({
title: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.title',
defaultMessage: 'Ease of management',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.description',
defaultMessage: 'Make it easier to manage your Mattermost workspace.',
}),
descriptionOk: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.descriptionOk',
defaultMessage: 'Your user authentication setup is appropriate based on your current usage!',
}),
icon: (
<div className='icon'>
<AccountMultipleOutlineIcon
size={20}
color={'var(--sys-center-channel-color)'}
/>
</div>
),
items: [
{
id: 'ad-ldap',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.ldap.title',
defaultMessage: 'AD/LDAP integration recommended',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.ldap.description',
defaultMessage: 'You\'ve reached over 100 users! We recommend setting up AD/LDAP user authentication for easier onboarding as well as automated deactivations and role assignments.',
}),
...(isLicensed && !isStarterLicense ? {
configUrl: ConsolePages.AD_LDAP,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.configureLDAP', defaultMessage: 'Try AD/LDAP'}),
} : trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.AD_LDAP,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'ad-ldap',
status: data.ldap.status,
scoreImpact: 22,
impactModifier: impactModifiers[data.ldap.status],
},
// commented out for now.
// @see discussion here: https://github.com/mattermost/mattermost-webapp/pull/9822#discussion_r806879385
// {
// id: 'guest-accounts',
// title: formatMessage({
// id: 'admin.reporting.workspace_optimization.ease_of_management.guests_accounts.title',
// defaultMessage: 'Guest Accounts recommended',
// }),
// description: formatMessage({
// id: 'admin.reporting.workspace_optimization.ease_of_management.guests_accounts.description',
// defaultMessage: 'Several user accounts are using different domains than your Site URL. You can control user access to channels and teams with guest accounts. We recommend starting an Enterprise trial and enabling Guest Access.',
// }),
// ...trialOrEnterpriseCtaConfig,
// infoUrl: 'https://docs.mattermost.com/onboard/guest-accounts.html',
// infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
// telemetryAction: 'guest-accounts',
// status: data.guestAccounts.status,
// scoreImpact: 6,
// impactModifier: impactModifiers[data.guestAccounts.status],
// },
],
});
return {getAccessData, getConfigurationData, getUpdatesData, getPerformanceData, getDataPrivacyData, getEaseOfManagementData, isLicensed, isEnterpriseLicense};
};
export default useMetricsData;

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

@@ -4,17 +4,11 @@
import {CheckIcon} from '@mattermost/compass-icons/components';
import classNames from 'classnames';
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import React from 'react';
import styled from 'styled-components';
import {GlobalState} from '@mattermost/types/store';
import {getServerVersion} from 'mattermost-redux/selectors/entities/general';
import {Client4} from 'mattermost-redux/client';
import Accordion, {AccordionItemType} from 'components/common/accordion/accordion';
import {elasticsearchTest, ldapTest, testSiteURL} from '../../../actions/admin_actions';
import LoadingScreen from '../../loading_screen';
import FormattedAdminHeader from '../../widgets/admin_console/formatted_admin_header';
import {Props} from '../admin_console';
@@ -22,10 +16,11 @@ import {Props} from '../admin_console';
import ChipsList, {ChipsInfoType} from './chips_list';
import CtaButtons from './cta_buttons';
import useMetricsData, {DataModel, ItemStatus, UpdatesParam} from './dashboard.data';
import useMetricsData from './dashboard.data';
import './dashboard.scss';
import OverallScore from './overall-score';
import {ItemStatus} from './dashboard.type';
const AccordionItem = styled.div`
padding: 12px;
@@ -49,180 +44,7 @@ const successIcon = (
);
const WorkspaceOptimizationDashboard = (props: Props) => {
const [loading, setLoading] = useState(true);
const [versionData, setVersionData] = useState<UpdatesParam['serverVersion']>({type: '', description: '', status: ItemStatus.NONE});
// const [guestAccountStatus, setGuestAccountStatus] = useState<ItemStatus>('none');
const [liveUrlStatus, setLiveUrlStatus] = useState<ItemStatus>(ItemStatus.ERROR);
const [elastisearchStatus, setElasticsearchStatus] = useState<ItemStatus>(ItemStatus.INFO);
const [ldapStatus, setLdapStatus] = useState<ItemStatus>(ItemStatus.INFO);
const [dataRetentionStatus, setDataRetentionStatus] = useState<ItemStatus>(ItemStatus.INFO);
const {formatMessage} = useIntl();
const {getAccessData, getConfigurationData, getUpdatesData, getPerformanceData, getDataPrivacyData, getEaseOfManagementData, isLicensed, isEnterpriseLicense} = useMetricsData();
// get the currently installed server version
const installedVersion = useSelector((state: GlobalState) => getServerVersion(state));
const analytics = useSelector((state: GlobalState) => state.entities.admin.analytics);
const {TOTAL_USERS: totalUsers, TOTAL_POSTS: totalPosts} = analytics!;
// gather locally available data
const {
ServiceSettings,
DataRetentionSettings,
ElasticsearchSettings,
LdapSettings,
// TeamSettings,
// GuestAccountsSettings,
} = props.config;
const {location} = document;
const sessionLengthWebInHours = ServiceSettings?.SessionLengthWebInHours || -1;
const testURL = () => {
if (!ServiceSettings?.SiteURL) {
return Promise.resolve();
}
const onSuccess = ({status}: any) => setLiveUrlStatus(status === 'OK' ? ItemStatus.OK : ItemStatus.ERROR);
const onError = () => setLiveUrlStatus(ItemStatus.ERROR);
return testSiteURL(onSuccess, onError, ServiceSettings?.SiteURL);
};
const testDataRetention = async () => {
if (!isLicensed || !isEnterpriseLicense) {
return Promise.resolve();
}
if (DataRetentionSettings?.EnableMessageDeletion || DataRetentionSettings?.EnableFileDeletion) {
setDataRetentionStatus(ItemStatus.OK);
return Promise.resolve();
}
const result = await fetch(`${Client4.getBaseRoute()}/data_retention/policies?page=0&per_page=0`).then((result) => result.json());
setDataRetentionStatus(result.total_count > 0 ? ItemStatus.OK : ItemStatus.INFO);
return Promise.resolve();
};
const fetchVersion = async () => {
const result = await fetch(`${Client4.getBaseRoute()}/latest_version`).then((result) => result.json());
if (result.tag_name) {
const sanitizedVersion = result.tag_name.startsWith('v') ? result.tag_name.slice(1) : result.tag_name;
const newVersionParts = sanitizedVersion.split('.');
const installedVersionParts = installedVersion.split('.').slice(0, 3);
// quick general check if a newer version is available
let type = '';
let status: ItemStatus = ItemStatus.OK;
if (newVersionParts.join('') > installedVersionParts.join('')) {
// get correct values to be inserted into the accordion item
switch (true) {
case newVersionParts[0] > installedVersionParts[0]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.major',
defaultMessage: 'Major',
});
status = ItemStatus.ERROR;
break;
case newVersionParts[1] > installedVersionParts[1]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.minor',
defaultMessage: 'Minor',
});
status = ItemStatus.WARNING;
break;
case newVersionParts[2] > installedVersionParts[2]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.patch',
defaultMessage: 'Patch',
});
status = ItemStatus.INFO;
break;
}
}
setVersionData({type, description: result.body, status});
}
};
const testElasticsearch = () => {
if (!isLicensed || !isEnterpriseLicense || !(ElasticsearchSettings?.EnableIndexing && ElasticsearchSettings?.EnableSearching)) {
return Promise.resolve();
}
const onSuccess = ({status}: any) => setElasticsearchStatus(status === 'OK' ? ItemStatus.OK : ItemStatus.INFO);
const onError = () => setElasticsearchStatus(ItemStatus.INFO);
return elasticsearchTest(props.config, onSuccess, onError);
};
const testLdap = () => {
if (!isLicensed || !LdapSettings?.Enable) {
return Promise.resolve();
}
const onSuccess = ({status}: any) => setLdapStatus(status === 'OK' ? ItemStatus.OK : ItemStatus.INFO);
const onError = () => setLdapStatus(ItemStatus.INFO);
return ldapTest(onSuccess, onError);
};
// commented out for now.
// @see discussion here: https://github.com/mattermost/mattermost-webapp/pull/9822#discussion_r806879385
// const fetchGuestAccounts = async () => {
// if (TeamSettings?.EnableOpenServer && GuestAccountsSettings?.Enable) {
// let usersArray = await fetch(`${Client4.getBaseRoute()}/users/invalid_emails`).then((result) => result.json());
//
// // this setting is just a string with a list of domains, or an empty string
// if (GuestAccountsSettings?.RestrictCreationToDomains) {
// const domainList = GuestAccountsSettings?.RestrictCreationToDomains;
// usersArray = usersArray.filter(({email}: Record<string, unknown>) => domainList.includes((email as string).split('@')[1]));
// }
//
// // if guest accounts make up more than 5% of the user base show the info accordion
// if (usersArray.length > (totalUsers as number * 0.05)) {
// setGuestAccountStatus(ItemStatus.INFO);
// return;
// }
// }
//
// setGuestAccountStatus(ItemStatus.OK);
// };
useEffect(() => {
const promises = [];
promises.push(testURL());
promises.push(testLdap());
promises.push(fetchVersion());
promises.push(testElasticsearch());
promises.push(testDataRetention());
// promises.push(fetchGuestAccounts());
Promise.all(promises).then(() => setLoading(false));
}, [props.config, isLicensed, isEnterpriseLicense]);
const data: DataModel = {
updates: getUpdatesData({serverVersion: versionData}),
configuration: getConfigurationData({
ssl: {status: location.protocol === 'https:' ? ItemStatus.OK : ItemStatus.ERROR},
sessionLength: {status: sessionLengthWebInHours === 720 ? ItemStatus.INFO : ItemStatus.OK},
}),
access: getAccessData({siteUrl: {status: liveUrlStatus}}),
performance: getPerformanceData({
search: {
status: totalPosts < 2_000_000 && totalUsers < 500 ? ItemStatus.OK : elastisearchStatus,
},
}),
dataPrivacy: getDataPrivacyData({retention: {status: dataRetentionStatus}}),
easyManagement: getEaseOfManagementData({
ldap: {status: totalUsers < 100 ? ItemStatus.OK : ldapStatus},
// guestAccounts: {status: guestAccountStatus},
}),
};
const {data, loading} = useMetricsData(props.config);
const overallScoreChips: ChipsInfoType = {
[ItemStatus.INFO]: 0,
@@ -236,7 +58,7 @@ const WorkspaceOptimizationDashboard = (props: Props) => {
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const accData: AccordionItemType[] = Object.entries(data).filter(([_, y]) => !y.hide).map(([accordionKey, accordionData]) => {
const accordionItemsData: AccordionItemType[] | undefined = data && Object.entries(data).filter(([_, y]) => !y.hide).map(([accordionKey, accordionData]) => {
const accordionDataChips: ChipsInfoType = {
[ItemStatus.INFO]: 0,
[ItemStatus.WARNING]: 0,
@@ -297,7 +119,7 @@ const WorkspaceOptimizationDashboard = (props: Props) => {
};
});
return loading ? <LoadingScreen/> : (
return loading || !accordionItemsData ? <LoadingScreen/> : (
<div className='WorkspaceOptimizationDashboard wrapper--fixed'>
<FormattedAdminHeader
id={'admin.reporting.workspace_optimization.title'}
@@ -314,7 +136,7 @@ const WorkspaceOptimizationDashboard = (props: Props) => {
chartValue={Math.floor((overallScore.current / overallScore.max) * 100)}
/>
<Accordion
accordionItemsData={accData}
accordionItemsData={accordionItemsData}
expandMultiple={true}
/>
</div>

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

@@ -0,0 +1,75 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export type DataModel = {
[key: string]: {
title: string;
description: string;
descriptionOk: string;
items: ItemModel[];
icon: React.ReactNode;
hide?: boolean;
};
}
export enum ItemStatus {
/** Return NONE if it's not relevant, not configured, or not an option */
NONE = 'none',
/** Return OK if all checks for this have passed */
OK = 'ok',
/** Return info if it might not be relevant to them, but they could utilize it */
INFO = 'info',
WARNING = 'warning',
ERROR = 'error',
}
export type ItemModel = {
id: string;
title: string;
description: string;
status: ItemStatus;
scoreImpact: number;
impactModifier: number;
configUrl?: string;
configText?: string;
telemetryAction?: string;
infoUrl?: string;
infoText?: string;
}
export type UpdatesParam = {
serverVersion: {
type: string;
status: ItemStatus;
description: string;
};
}
type Analytics = {
DAILY_ACTIVE_USERS: number;
MONTHLY_ACTIVE_USERS: number;
TOTAL_INACTIVE_USERS: number;
TOTAL_MASTER_DB_CONNECTIONS: number;
TOTAL_POSTS: number;
TOTAL_PRIVATE_GROUPS: number;
TOTAL_PUBLIC_CHANNELS: number;
TOTAL_READ_DB_CONNECTIONS: number;
TOTAL_TEAMS: number;
TOTAL_USERS: number;
TOTAL_WEBSOCKET_CONNECTIONS: number;
}
export type Options = {
isLicensed: boolean;
isEnterpriseLicense: boolean;
trialOrEnterpriseCtaConfig: {
configUrl: string;
configText: string;
};
isCloud: boolean;
isStarterLicense: boolean;
analytics: Analytics | undefined;
installedVersion: string;
};

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

@@ -0,0 +1,66 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useIntl} from 'react-intl';
import {ItemModel, ItemStatus} from '../dashboard.type';
import {ConsolePages, DocLinks} from 'utils/constants';
import {impactModifiers} from '../dashboard.data';
import {AdminConfig} from '@mattermost/types/config';
import {testSiteURL} from 'actions/admin_actions';
/**
*
* @description Checking to see if the siteURL is configured correctly by running it through the same "check siteURL" button that exists on the webserver page.
*/
const siteURLCheck = async (config: Partial<AdminConfig>, formatMessage: ReturnType<typeof useIntl>['formatMessage']): Promise<ItemModel> => {
let status = ItemStatus.OK;
const testURL = async () => {
if (!config.ServiceSettings?.SiteURL) {
status = ItemStatus.ERROR;
}
const onSuccess = ({status: s}: any) => {
if (s === 'OK') {
status = ItemStatus.OK;
}
};
const onError = () => {
status = ItemStatus.ERROR;
};
await testSiteURL(onSuccess, onError, config.ServiceSettings?.SiteURL);
};
await testURL();
return {
id: 'site-url',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.access.site_url.title',
defaultMessage: 'Misconfigured web server',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.access.site_url.description',
defaultMessage: 'Your web server settings aren\'t passing a live URL test which means your workspace may not be accessible to users. We recommend updating your web server settings.',
}),
configUrl: ConsolePages.WEB_SERVER,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.access.site_url.cta', defaultMessage: 'Configure web server'}),
infoUrl: DocLinks.SITE_URL,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'site-url',
status,
scoreImpact: 12,
impactModifier: impactModifiers[status],
};
};
const checks = [
siteURLCheck,
];
export const runAccessChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
) => {
const results = await Promise.all(checks.map((check) => check(config, formatMessage)));
return results;
};

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

@@ -0,0 +1,84 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable @typescript-eslint/no-unused-vars */
import {AdminConfig} from '@mattermost/types/config';
import {useIntl} from 'react-intl';
import {ConsolePages, DocLinks} from 'utils/constants';
import {ItemModel, ItemStatus, Options} from '../dashboard.type';
import {impactModifiers} from '../dashboard.data';
/**
*
* @description This checks to see if the user's active session is done over https. This does not check if the server is configured to use https.
*/
const ssl = (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
): ItemModel => {
const status = document.location.protocol === 'https:' ? ItemStatus.OK : ItemStatus.ERROR;
return {
id: 'ssl',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.ssl.title',
defaultMessage: 'Configure SSL to make your server more secure',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.ssl.description',
defaultMessage: 'We strongly recommend securing your Mattermost workspace by configuring SSL in production environments.',
}),
infoUrl: DocLinks.SSL_CERTIFICATE,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'ssl',
status,
scoreImpact: 25,
impactModifier: impactModifiers[status],
};
};
/**
*
* @description This checks to see if the user has adjusted the default session lengths to something other than 720 hours.
*/
const sessionLength = (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
): ItemModel => {
const status = config.ServiceSettings?.SessionLengthMobileInHours === 720 ? ItemStatus.INFO : ItemStatus.OK;
return {
id: 'session-length',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.session_length.title',
defaultMessage: 'Session lengths is set to default',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.configuration.session_length.description',
defaultMessage: 'Your session length is set to the default of 30 days. A longer session length provides convenience, and a shorter session provides tighter security. We recommend adjusting this based on your organization\'s security policies.',
}),
configUrl: ConsolePages.SESSION_LENGTHS,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.configuration.session_length.cta', defaultMessage: 'Configure session length'}),
infoUrl: DocLinks.SESSION_LENGTHS,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'session-length',
status,
scoreImpact: 8,
impactModifier: impactModifiers[status],
};
};
export const runConfigChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const checks = [
ssl,
sessionLength,
];
const results = await Promise.all(checks.map((check) => check(config, formatMessage, options)));
return results;
};

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

@@ -0,0 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useIntl} from 'react-intl';
import {impactModifiers} from '../dashboard.data';
import {ConsolePages, DocLinks} from 'utils/constants';
import {Client4} from 'mattermost-redux/client';
import {ItemStatus, Options} from '../dashboard.type';
import {AdminConfig} from '@mattermost/types/config';
/**
*
* @description Checks if they they have a global policy deletion enabled, or if a custom policy has been created.
*/
const dataRetentionCheck = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const testDataRetention = async (
config: Partial<AdminConfig>,
options: Options,
) => {
if (!options.isLicensed || !options.isEnterpriseLicense) {
return ItemStatus.INFO;
}
if (config.DataRetentionSettings?.EnableMessageDeletion || config.DataRetentionSettings?.EnableFileDeletion) {
return ItemStatus.OK;
}
const policyCount: {total_count: number} = await fetch(`${Client4.getBaseRoute()}/data_retention/policies_count`).then((result) => result.json());
return policyCount.total_count > 0 ? ItemStatus.OK : ItemStatus.INFO;
};
const status = await testDataRetention(config, options);
return {
id: 'data-retention',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.retention.title',
defaultMessage: 'Become more data aware',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.data_privacy.retention.description',
defaultMessage: 'Organizations in highly regulated industries require more control and insight with their data. We recommend enabling Data Retention and Compliance features.',
}),
...(options.isLicensed && options.isEnterpriseLicense ? {
configUrl: ConsolePages.DATA_RETENTION,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.data_privacy.retention.cta', defaultMessage: 'Try data retention'}),
} : options.trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.DATA_RETENTION_POLICY,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'data-retention',
status,
scoreImpact: 16,
impactModifier: impactModifiers[status],
};
};
export const runDataPrivacyChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const checks = [
dataRetentionCheck,
];
const results = await Promise.all(checks.map((check) => check(config, formatMessage, options)));
return results;
};

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

@@ -0,0 +1,129 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AdminConfig} from '@mattermost/types/config';
import {useIntl} from 'react-intl';
import {ItemModel, ItemStatus, Options} from '../dashboard.type';
import {ConsolePages, DocLinks} from 'utils/constants';
import {impactModifiers} from '../dashboard.data';
// import {Client4} from 'mattermost-redux/client';
// import {AnalyticsRow} from '@mattermost/types/admin';
import {ldapTest} from 'actions/admin_actions';
const usesLDAP = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
): Promise<ItemModel> => {
const testLdap = async (
config: Partial<AdminConfig>,
options: Options,
): Promise<ItemStatus> => {
let check = ItemStatus.INFO;
if (!options.isLicensed || !config.LdapSettings?.Enable) {
return check;
}
const onSuccess = ({status}: any) => {
if (status === 'OK') {
check = ItemStatus.OK;
}
};
await ldapTest(onSuccess);
return check;
};
// something feels flawed in this check.
const status = options.analytics?.TOTAL_USERS as number > 100 ? await testLdap(config, options) : ItemStatus.OK;
return {
id: 'ad-ldap',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.ldap.title',
defaultMessage: 'AD/LDAP integration recommended',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.ease_of_management.ldap.description',
defaultMessage: 'You\'ve reached over 100 users! We recommend setting up AD/LDAP user authentication for easier onboarding as well as automated deactivations and role assignments.',
}),
...(options.isLicensed && !options.isStarterLicense ? {
configUrl: ConsolePages.AD_LDAP,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.ease_of_management.ldap.cta', defaultMessage: 'Try AD/LDAP'}),
} : options.trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.AD_LDAP,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'ad-ldap',
status,
scoreImpact: 22,
impactModifier: impactModifiers[status],
};
};
// // commented out for now.
// // @see discussion here: https://github.com/mattermost/mattermost-webapp/pull/9822#discussion_r806879385
// const fetchGuestAccounts = async (
// config: Partial<AdminConfig>,
// analytics: Record<string, number | AnalyticsRow[]> | undefined,
// ) => {
// if (config.TeamSettings?.EnableOpenServer && config.GuestAccountsSettings?.Enable) {
// let usersArray = await fetch(`${Client4.getBaseRoute()}/users/invalid_emails`).then((result) => result.json());
// // this setting is just a string with a list of domains, or an empty string
// if (config.GuestAccountsSettings?.RestrictCreationToDomains) {
// const domainList = config.GuestAccountsSettings?.RestrictCreationToDomains;
// usersArray = usersArray.filter(({email}: Record<string, unknown>) => domainList.includes((email as string).split('@')[1]));
// }
// // if guest accounts make up more than 5% of the user base show the info accordion
// if (analytics && usersArray.length > (analytics.totalUsers as number * 0.05)) {
// return ItemStatus.INFO;
// }
// }
// return ItemStatus.OK;
// };
// const guestAccounts = async (
// config: Partial<AdminConfig>,
// formatMessage: ReturnType<typeof useIntl>['formatMessage'],
// options: Options,
// ): Promise<ItemModel> => {
// const status = await fetchGuestAccounts(config, options.analytics);
// return {
// id: 'guest-accounts',
// title: formatMessage({
// id: 'admin.reporting.workspace_optimization.ease_of_management.guests_accounts.title',
// defaultMessage: 'Guest Accounts recommended',
// }),
// description: formatMessage({
// id: 'admin.reporting.workspace_optimization.ease_of_management.guests_accounts.description',
// defaultMessage: 'Several user accounts are using different domains than your Site URL. You can control user access to channels and teams with guest accounts. We recommend starting an Enterprise trial and enabling Guest Access.',
// }),
// ...options.trialOrEnterpriseCtaConfig,
// infoUrl: 'https://docs.mattermost.com/onboard/guest-accounts.html',
// infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
// telemetryAction: 'guest-accounts',
// status,
// scoreImpact: 6,
// impactModifier: impactModifiers[status],
// };
// };
export const runEaseOfUseChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
): Promise<ItemModel[]> => {
const checks = [
usesLDAP,
// guestAccounts,
];
const results = await Promise.all(checks.map((check) => check(config, formatMessage, options)));
return results;
};

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

@@ -0,0 +1,72 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AdminConfig} from '@mattermost/types/config';
import {ItemModel, ItemStatus, Options} from '../dashboard.type';
import {elasticsearchTest} from 'actions/admin_actions';
import {useIntl} from 'react-intl';
import {ConsolePages, DocLinks} from 'utils/constants';
import {impactModifiers} from '../dashboard.data';
const search = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
): Promise<ItemModel> => {
const testElasticsearch = async (
config: Partial<AdminConfig>,
options: Options,
) => {
let check = ItemStatus.INFO;
if (!options.isLicensed || !options.isEnterpriseLicense || !(config.ElasticsearchSettings?.EnableIndexing && config.ElasticsearchSettings?.EnableSearching)) {
return check;
}
const onSuccess = ({status}: any) => {
if (status === 'OK') {
check = ItemStatus.OK;
}
};
await elasticsearchTest(config, onSuccess);
return check;
};
const totalPosts = options.analytics?.TOTAL_POSTS as number;
const totalUsers = options.analytics?.TOTAL_USERS as number;
const status = totalPosts < 2_000_000 && totalUsers < 500 ? ItemStatus.OK : await testElasticsearch(config, options);
return {
id: 'search',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.search.title',
defaultMessage: 'Search performance',
}),
description: formatMessage({
id: 'admin.reporting.workspace_optimization.performance.search.description',
defaultMessage: 'Your server has reached over 500 users and 2 million posts which can result in slow search performance. We recommend enabling Elasticsearch for better performance.',
}),
...(options.isLicensed && options.isEnterpriseLicense ? {
configUrl: ConsolePages.ELASTICSEARCH,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.search.cta', defaultMessage: 'Try Elasticsearch'}),
} : options.trialOrEnterpriseCtaConfig),
infoUrl: DocLinks.ELASTICSEARCH,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'search-optimization',
status,
scoreImpact: 20,
impactModifier: impactModifiers[status],
};
};
export const runPerformanceChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const checks = [
search,
];
const results = await Promise.all(checks.map((check) => check(config, formatMessage, options)));
return results;
};

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

@@ -0,0 +1,94 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Client4} from 'mattermost-redux/client';
import {ItemStatus, Options} from '../dashboard.type';
import {useIntl} from 'react-intl';
import {AdminConfig} from '@mattermost/types/config';
import {CloudLinks, DocLinks} from 'utils/constants';
import {impactModifiers} from '../dashboard.data';
const testServerVersion = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const fetchVersion = async (
installedVersion: string,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
) => {
const result = await fetch(`${Client4.getBaseRoute()}/latest_version`).then((result) => result.json());
if (result.tag_name) {
const sanitizedVersion = result.tag_name.startsWith('v') ? result.tag_name.slice(1) : result.tag_name;
const newVersionParts = sanitizedVersion.split('.');
const installedVersionParts = installedVersion.split('.').slice(0, 3);
// quick general check if a newer version is available
let type = '';
let status: ItemStatus = ItemStatus.OK;
if (newVersionParts.join('') > installedVersionParts.join('')) {
// get correct values to be inserted into the accordion item
switch (true) {
case newVersionParts[0] > installedVersionParts[0]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.major',
defaultMessage: 'Major',
});
status = ItemStatus.ERROR;
break;
case newVersionParts[1] > installedVersionParts[1]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.minor',
defaultMessage: 'Minor',
});
status = ItemStatus.WARNING;
break;
case newVersionParts[2] > installedVersionParts[2]:
type = formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.patch',
defaultMessage: 'Patch',
});
status = ItemStatus.INFO;
break;
}
}
return {type, description: result.body, status};
}
return {type: '', description: '', status: ItemStatus.OK};
};
const serverVersion = await fetchVersion(options.installedVersion, formatMessage);
return {
id: 'server_version',
title: formatMessage({
id: 'admin.reporting.workspace_optimization.updates.server_version.status.title',
defaultMessage: '{type} version update available.',
}, {type: serverVersion.type}),
description: serverVersion.description,
configUrl: CloudLinks.DOWNLOAD_UPDATE,
configText: formatMessage({id: 'admin.reporting.workspace_optimization.updates.server_version.cta', defaultMessage: 'Download update'}),
infoUrl: DocLinks.UPGRADE_SERVER,
infoText: formatMessage({id: 'admin.reporting.workspace_optimization.cta.learnMore', defaultMessage: 'Learn more'}),
telemetryAction: 'server-version',
status: serverVersion.status,
scoreImpact: 15,
impactModifier: impactModifiers[serverVersion.status],
};
};
export const runUpdateChecks = async (
config: Partial<AdminConfig>,
formatMessage: ReturnType<typeof useIntl>['formatMessage'],
options: Options,
) => {
const checks = [
testServerVersion,
];
const results = await Promise.all(checks.map((check) => check(config, formatMessage, options)));
return results;
};

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

@@ -1944,6 +1944,7 @@
"admin.reload.reloadFail": "Reloading unsuccessful: {error}",
"admin.reporting.workspace_optimization.access.description": "Web server configuration may be affecting access to your Mattermost workspace.",
"admin.reporting.workspace_optimization.access.descriptionOk": "Access to your workspace seems available!",
"admin.reporting.workspace_optimization.access.site_url.cta": "Configure web server",
"admin.reporting.workspace_optimization.access.site_url.description": "Your web server settings aren't passing a live URL test which means your workspace may not be accessible to users. We recommend updating your web server settings.",
"admin.reporting.workspace_optimization.access.site_url.title": "Misconfigured web server",
"admin.reporting.workspace_optimization.access.title": "Workspace access",
@@ -1952,27 +1953,24 @@
"admin.reporting.workspace_optimization.chip_warnings": "Warnings: {count}",
"admin.reporting.workspace_optimization.configuration.description": "You have configuration issues to resolve",
"admin.reporting.workspace_optimization.configuration.descriptionOk": "You seem to have good configuration for SSL and Session Lengths!",
"admin.reporting.workspace_optimization.configuration.session_length.cta": "Configure session length",
"admin.reporting.workspace_optimization.configuration.session_length.description": "Your session length is set to the default of 30 days. A longer session length provides convenience, and a shorter session provides tighter security. We recommend adjusting this based on your organization's security policies.",
"admin.reporting.workspace_optimization.configuration.session_length.title": "Session lengths is set to default",
"admin.reporting.workspace_optimization.configuration.ssl.description": "We strongly recommend securing your Mattermost workspace by configuring SSL in production environments.",
"admin.reporting.workspace_optimization.configuration.ssl.title": "Configure SSL to make your server more secure",
"admin.reporting.workspace_optimization.configuration.title": "Configuration",
"admin.reporting.workspace_optimization.cta.configureDataRetention": "Try data retention",
"admin.reporting.workspace_optimization.cta.configureElasticsearch": "Try Elasticsearch",
"admin.reporting.workspace_optimization.cta.configureLDAP": "Try AD/LDAP",
"admin.reporting.workspace_optimization.cta.configureSessionLength": "Configure session length",
"admin.reporting.workspace_optimization.cta.configureWebServer": "Configure web server",
"admin.reporting.workspace_optimization.cta.downloadUpdate": "Download update",
"admin.reporting.workspace_optimization.cta.learnMore": "Learn more",
"admin.reporting.workspace_optimization.cta.startTrial": "Start trial",
"admin.reporting.workspace_optimization.cta.upgradeLicense": "Contact sales",
"admin.reporting.workspace_optimization.data_privacy.description": "Get better insight and control over your data.",
"admin.reporting.workspace_optimization.data_privacy.descriptionOk": "You seem to be using data retention and compliance features!",
"admin.reporting.workspace_optimization.data_privacy.retention.cta": "Try data retention",
"admin.reporting.workspace_optimization.data_privacy.retention.description": "Organizations in highly regulated industries require more control and insight with their data. We recommend enabling Data Retention and Compliance features.",
"admin.reporting.workspace_optimization.data_privacy.retention.title": "Become more data aware",
"admin.reporting.workspace_optimization.data_privacy.title": "Data privacy",
"admin.reporting.workspace_optimization.ease_of_management.description": "Make it easier to manage your Mattermost workspace.",
"admin.reporting.workspace_optimization.ease_of_management.descriptionOk": "Your user authentication setup is appropriate based on your current usage!",
"admin.reporting.workspace_optimization.ease_of_management.ldap.cta": "Try AD/LDAP",
"admin.reporting.workspace_optimization.ease_of_management.ldap.description": "You've reached over 100 users! We recommend setting up AD/LDAP user authentication for easier onboarding as well as automated deactivations and role assignments.",
"admin.reporting.workspace_optimization.ease_of_management.ldap.title": "AD/LDAP integration recommended",
"admin.reporting.workspace_optimization.ease_of_management.title": "Ease of management",
@@ -1983,9 +1981,11 @@
"admin.reporting.workspace_optimization.performance.search.description": "Your server has reached over 500 users and 2 million posts which can result in slow search performance. We recommend enabling Elasticsearch for better performance.",
"admin.reporting.workspace_optimization.performance.search.title": "Search performance",
"admin.reporting.workspace_optimization.performance.title": "Performance",
"admin.reporting.workspace_optimization.search.cta": "Try Elasticsearch",
"admin.reporting.workspace_optimization.title": "Workspace Optimization",
"admin.reporting.workspace_optimization.updates.description": "An update is available.",
"admin.reporting.workspace_optimization.updates.descriptionOk": "Your workspace is completely up to date!",
"admin.reporting.workspace_optimization.updates.server_version.cta": "Download update",
"admin.reporting.workspace_optimization.updates.server_version.status.title": "{type} version update available.",
"admin.reporting.workspace_optimization.updates.server_version.update_type.major": "Major",
"admin.reporting.workspace_optimization.updates.server_version.update_type.minor": "Minor",