[MM-50352] Improve URL validation and add/edit server experience (#2720)
* [MM-50352] Improve URL validation and add/edit server experience * Fix build * Fix translations * First pass of fixes * Some changes to avoid 2 clicks, tests * PR feedback * Update translations * PR feedback * Fix translations * PR feedback * E2E test fixes
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useCallback, useEffect} from 'react';
|
||||
import React, {useState, useCallback, useEffect, useRef} from 'react';
|
||||
import {useIntl, FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import {isValidURL, parseURL} from 'common/utils/url';
|
||||
import {MODAL_TRANSITION_TIMEOUT} from 'common/utils/constants';
|
||||
import {MODAL_TRANSITION_TIMEOUT, URLValidationStatus} from 'common/utils/constants';
|
||||
|
||||
import womanLaptop from 'renderer/assets/svg/womanLaptop.svg';
|
||||
|
||||
@@ -22,7 +21,6 @@ import 'renderer/css/components/ConfigureServer.scss';
|
||||
import 'renderer/css/components/LoadingScreen.css';
|
||||
|
||||
type ConfigureServerProps = {
|
||||
currentServers: UniqueServer[];
|
||||
server?: UniqueServer;
|
||||
mobileView?: boolean;
|
||||
darkMode?: boolean;
|
||||
@@ -36,7 +34,6 @@ type ConfigureServerProps = {
|
||||
};
|
||||
|
||||
function ConfigureServer({
|
||||
currentServers,
|
||||
server,
|
||||
mobileView,
|
||||
darkMode,
|
||||
@@ -56,35 +53,62 @@ function ConfigureServer({
|
||||
id,
|
||||
} = server || {};
|
||||
|
||||
const mounted = useRef(false);
|
||||
const [transition, setTransition] = useState<'inFromRight' | 'outToLeft'>();
|
||||
const [name, setName] = useState(prevName || '');
|
||||
const [url, setUrl] = useState(prevURL || '');
|
||||
const [nameError, setNameError] = useState('');
|
||||
const [urlError, setURLError] = useState('');
|
||||
const [urlError, setURLError] = useState<{type: STATUS; value: string}>();
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
|
||||
const canSave = name && url && !nameError && !urlError;
|
||||
const [validating, setValidating] = useState(false);
|
||||
const validationTimestamp = useRef<number>();
|
||||
const validationTimeout = useRef<NodeJS.Timeout>();
|
||||
const editing = useRef(false);
|
||||
|
||||
const canSave = name && url && !nameError && !validating && urlError && urlError.type !== STATUS.ERROR;
|
||||
|
||||
useEffect(() => {
|
||||
setTransition('inFromRight');
|
||||
setShowContent(true);
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checkProtocolInURL = (checkURL: string): Promise<string> => {
|
||||
if (isValidURL(checkURL)) {
|
||||
return Promise.resolve(checkURL);
|
||||
}
|
||||
return window.desktop.modals.pingDomain(checkURL).
|
||||
then((result: string) => {
|
||||
const newURL = `${result}://${checkURL}`;
|
||||
setUrl(newURL);
|
||||
return newURL;
|
||||
}).
|
||||
catch(() => {
|
||||
console.error(`Could not ping url: ${checkURL}`);
|
||||
return checkURL;
|
||||
});
|
||||
const fetchValidationResult = (urlToValidate: string) => {
|
||||
setValidating(true);
|
||||
setURLError({
|
||||
type: STATUS.INFO,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.validating', defaultMessage: 'Validating...'}),
|
||||
});
|
||||
const requestTime = Date.now();
|
||||
validationTimestamp.current = requestTime;
|
||||
validateURL(urlToValidate).then(({validatedURL, serverName, message}) => {
|
||||
if (editing.current) {
|
||||
setValidating(false);
|
||||
setURLError(undefined);
|
||||
return;
|
||||
}
|
||||
if (!validationTimestamp.current || requestTime < validationTimestamp.current) {
|
||||
return;
|
||||
}
|
||||
if (validatedURL) {
|
||||
setUrl(validatedURL);
|
||||
}
|
||||
if (serverName) {
|
||||
setName((prev) => {
|
||||
return prev.length ? prev : serverName;
|
||||
});
|
||||
}
|
||||
if (message) {
|
||||
setTransition(undefined);
|
||||
setURLError(message);
|
||||
}
|
||||
setValidating(false);
|
||||
});
|
||||
};
|
||||
|
||||
const validateName = () => {
|
||||
@@ -97,46 +121,76 @@ function ConfigureServer({
|
||||
});
|
||||
}
|
||||
|
||||
if (currentServers.find(({name: existingName}) => existingName === newName)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.serverNameExists',
|
||||
defaultMessage: 'A server with the same name already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const validateURL = async (fullURL: string) => {
|
||||
if (!fullURL) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlRequired',
|
||||
defaultMessage: 'URL is required.',
|
||||
});
|
||||
const validateURL = async (url: string) => {
|
||||
let message;
|
||||
const validationResult = await window.desktop.validateServerURL(url);
|
||||
if (validationResult.validatedURL) {
|
||||
setUrl(validationResult.validatedURL);
|
||||
}
|
||||
|
||||
if (!parseURL(fullURL)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlIncorrectFormatting',
|
||||
defaultMessage: 'URL is not formatted correctly.',
|
||||
});
|
||||
if (validationResult?.status === URLValidationStatus.Missing) {
|
||||
message = {
|
||||
type: STATUS.ERROR,
|
||||
value: formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlRequired',
|
||||
defaultMessage: 'URL is required.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!isValidURL(fullURL)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlNeedsHttp',
|
||||
defaultMessage: 'URL should start with http:// or https://.',
|
||||
});
|
||||
if (validationResult?.status === URLValidationStatus.Invalid) {
|
||||
message = {
|
||||
type: STATUS.ERROR,
|
||||
value: formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlIncorrectFormatting',
|
||||
defaultMessage: 'URL is not formatted correctly.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (currentServers.find(({url: existingURL}) => parseURL(existingURL)?.toString === parseURL(fullURL)?.toString())) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.serverUrlExists',
|
||||
defaultMessage: 'A server with the same URL already exists.',
|
||||
});
|
||||
if (validationResult?.status === URLValidationStatus.Insecure) {
|
||||
message = {
|
||||
type: STATUS.WARNING,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.insecure', defaultMessage: 'Your server URL is potentially insecure. For best results, use a URL with the HTTPS protocol.'}),
|
||||
};
|
||||
}
|
||||
|
||||
return '';
|
||||
if (validationResult?.status === URLValidationStatus.NotMattermost) {
|
||||
message = {
|
||||
type: STATUS.WARNING,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.notMattermost', defaultMessage: 'The server URL provided does not appear to point to a valid Mattermost server. Please verify the URL and check your connection.'}),
|
||||
};
|
||||
}
|
||||
|
||||
if (validationResult?.status === URLValidationStatus.URLNotMatched) {
|
||||
message = {
|
||||
type: STATUS.WARNING,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.urlNotMatched', defaultMessage: 'The server URL provided does not match the configured Site URL on your Mattermost server. Server version: {serverVersion}'}, {serverVersion: validationResult.serverVersion}),
|
||||
};
|
||||
}
|
||||
|
||||
if (validationResult?.status === URLValidationStatus.URLUpdated) {
|
||||
message = {
|
||||
type: STATUS.INFO,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.urlUpdated', defaultMessage: 'The server URL provided has been updated to match the configured Site URL on your Mattermost server. Server version: {serverVersion}'}, {serverVersion: validationResult.serverVersion}),
|
||||
};
|
||||
}
|
||||
|
||||
if (validationResult?.status === URLValidationStatus.OK) {
|
||||
message = {
|
||||
type: STATUS.SUCCESS,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.ok', defaultMessage: 'Server URL is valid. Server version: {serverVersion}'}, {serverVersion: validationResult.serverVersion}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
validatedURL: validationResult.validatedURL,
|
||||
serverName: validationResult.serverName,
|
||||
message,
|
||||
};
|
||||
};
|
||||
|
||||
const handleNameOnChange = ({target: {value}}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -151,8 +205,18 @@ function ConfigureServer({
|
||||
setUrl(value);
|
||||
|
||||
if (urlError) {
|
||||
setURLError('');
|
||||
setURLError(undefined);
|
||||
}
|
||||
|
||||
editing.current = true;
|
||||
clearTimeout(validationTimeout.current as unknown as number);
|
||||
validationTimeout.current = setTimeout(() => {
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
editing.current = false;
|
||||
fetchValidationResult(value);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleOnSaveButtonClick = (e: React.MouseEvent) => {
|
||||
@@ -183,21 +247,11 @@ function ConfigureServer({
|
||||
return;
|
||||
}
|
||||
|
||||
const fullURL = await checkProtocolInURL(url.trim());
|
||||
const urlError = await validateURL(fullURL);
|
||||
|
||||
if (urlError) {
|
||||
setTransition(undefined);
|
||||
setURLError(urlError);
|
||||
setWaiting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setTransition('outToLeft');
|
||||
|
||||
setTimeout(() => {
|
||||
onConnect({
|
||||
url: fullURL,
|
||||
url,
|
||||
name,
|
||||
id,
|
||||
});
|
||||
@@ -269,7 +323,7 @@ function ConfigureServer({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames('ConfigureServer__card', transition, {'with-error': nameError || urlError})}>
|
||||
<div className={classNames('ConfigureServer__card', transition, {'with-error': nameError || urlError?.type === STATUS.ERROR})}>
|
||||
<div
|
||||
className='ConfigureServer__card-content'
|
||||
onKeyDown={handleOnCardEnterKeyDown}
|
||||
@@ -286,10 +340,7 @@ function ConfigureServer({
|
||||
inputSize={SIZE.LARGE}
|
||||
value={url}
|
||||
onChange={handleURLOnChange}
|
||||
customMessage={urlError ? ({
|
||||
type: STATUS.ERROR,
|
||||
value: urlError,
|
||||
}) : ({
|
||||
customMessage={urlError ?? ({
|
||||
type: STATUS.INFO,
|
||||
value: formatMessage({id: 'renderer.components.configureServer.url.info', defaultMessage: 'The URL of your Mattermost server'}),
|
||||
})}
|
||||
@@ -321,7 +372,10 @@ function ConfigureServer({
|
||||
extraClasses='ConfigureServer__card-form-button'
|
||||
saving={waiting}
|
||||
onClick={handleOnSaveButtonClick}
|
||||
defaultMessage={formatMessage({id: 'renderer.components.configureServer.connect.default', defaultMessage: 'Connect'})}
|
||||
defaultMessage={urlError?.type === STATUS.WARNING ?
|
||||
formatMessage({id: 'renderer.components.configureServer.connect.override', defaultMessage: 'Connect anyway'}) :
|
||||
formatMessage({id: 'renderer.components.configureServer.connect.default', defaultMessage: 'Connect'})
|
||||
}
|
||||
savingMessage={formatMessage({id: 'renderer.components.configureServer.connect.saving', defaultMessage: 'Connecting…'})}
|
||||
disabled={!canSave}
|
||||
darkMode={darkMode}
|
||||
|
@@ -22,7 +22,7 @@ export enum SIZE {
|
||||
}
|
||||
|
||||
export type CustomMessageInputType = {
|
||||
type: 'info' | 'error' | 'warning' | 'success';
|
||||
type: STATUS;
|
||||
value: string;
|
||||
} | null;
|
||||
|
||||
|
@@ -3,18 +3,20 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Modal, Button, FormGroup, FormControl, FormLabel, FormText} from 'react-bootstrap';
|
||||
import {Modal, Button, FormGroup, FormControl, FormLabel, FormText, Spinner} from 'react-bootstrap';
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
||||
|
||||
import {UniqueServer} from 'types/config';
|
||||
import {URLValidationResult} from 'types/server';
|
||||
|
||||
import {isValidURL} from 'common/utils/url';
|
||||
import {URLValidationStatus} from 'common/utils/constants';
|
||||
|
||||
import 'renderer/css/components/NewServerModal.scss';
|
||||
|
||||
type Props = {
|
||||
onClose?: () => void;
|
||||
onSave?: (server: UniqueServer) => void;
|
||||
server?: UniqueServer;
|
||||
currentServers?: UniqueServer[];
|
||||
editMode?: boolean;
|
||||
show?: boolean;
|
||||
restoreFocus?: boolean;
|
||||
@@ -29,11 +31,15 @@ type State = {
|
||||
serverId?: string;
|
||||
serverOrder: number;
|
||||
saveStarted: boolean;
|
||||
validationStarted: boolean;
|
||||
validationResult?: URLValidationResult;
|
||||
}
|
||||
|
||||
class NewServerModal extends React.PureComponent<Props, State> {
|
||||
wasShown?: boolean;
|
||||
serverUrlInputRef?: HTMLInputElement;
|
||||
validationTimeout?: NodeJS.Timeout;
|
||||
mounted: boolean;
|
||||
|
||||
static defaultProps = {
|
||||
restoreFocus: true,
|
||||
@@ -43,48 +49,37 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.wasShown = false;
|
||||
this.mounted = false;
|
||||
this.state = {
|
||||
serverName: '',
|
||||
serverUrl: '',
|
||||
serverOrder: props.currentOrder || 0,
|
||||
saveStarted: false,
|
||||
validationStarted: false,
|
||||
};
|
||||
}
|
||||
|
||||
initializeOnShow() {
|
||||
componentDidMount(): void {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
initializeOnShow = () => {
|
||||
this.setState({
|
||||
serverName: this.props.server ? this.props.server.name : '',
|
||||
serverUrl: this.props.server ? this.props.server.url : '',
|
||||
serverId: this.props.server?.id,
|
||||
saveStarted: false,
|
||||
validationStarted: false,
|
||||
validationResult: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
getServerNameValidationError() {
|
||||
if (!this.state.saveStarted) {
|
||||
return null;
|
||||
if (this.props.editMode && this.props.server) {
|
||||
this.validateServerURL(this.props.server.url);
|
||||
}
|
||||
if (this.props.currentServers) {
|
||||
const currentServers = [...this.props.currentServers];
|
||||
if (currentServers.find((server) => server.id !== this.state.serverId && server.name === this.state.serverName)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.serverNameExists'
|
||||
defaultMessage='A server with the same name already exists.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.state.serverName.length > 0 ? null : (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.nameRequired'
|
||||
defaultMessage='Name is required.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getServerNameValidationState() {
|
||||
return this.getServerNameValidationError() === null ? null : 'error';
|
||||
}
|
||||
|
||||
handleServerNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -93,108 +88,205 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
getServerUrlValidationError() {
|
||||
if (!this.state.saveStarted) {
|
||||
return null;
|
||||
}
|
||||
if (this.props.currentServers) {
|
||||
const currentServers = [...this.props.currentServers];
|
||||
if (currentServers.find((server) => server.id !== this.state.serverId && server.url === this.state.serverUrl)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.serverUrlExists'
|
||||
defaultMessage='A server with the same URL already exists.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.state.serverUrl.length === 0) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlRequired'
|
||||
defaultMessage='URL is required.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!(/^https?:\/\/.*/).test(this.state.serverUrl.trim())) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlNeedsHttp'
|
||||
defaultMessage='URL should start with http:// or https://.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!isValidURL(this.state.serverUrl.trim())) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlIncorrectFormatting'
|
||||
defaultMessage='URL is not formatted correctly.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getServerUrlValidationState() {
|
||||
return this.getServerUrlValidationError() === null ? null : 'error';
|
||||
}
|
||||
|
||||
handleServerUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const serverUrl = e.target.value;
|
||||
this.setState({serverUrl});
|
||||
this.setState({serverUrl, validationResult: undefined});
|
||||
this.validateServerURL(serverUrl);
|
||||
}
|
||||
|
||||
addProtocolToUrl = (serverUrl: string): Promise<void> => {
|
||||
if (serverUrl.startsWith('http://') || serverUrl.startsWith('https://')) {
|
||||
return Promise.resolve(undefined);
|
||||
validateServerURL = (serverUrl: string) => {
|
||||
clearTimeout(this.validationTimeout as unknown as number);
|
||||
this.validationTimeout = setTimeout(() => {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
const currentTimeout = this.validationTimeout;
|
||||
this.setState({validationStarted: true});
|
||||
window.desktop.validateServerURL(serverUrl, this.props.server?.id).then((validationResult) => {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
if (currentTimeout !== this.validationTimeout) {
|
||||
return;
|
||||
}
|
||||
this.setState({validationResult, validationStarted: false, serverUrl: validationResult.validatedURL ?? serverUrl, serverName: this.state.serverName ? this.state.serverName : validationResult.serverName ?? ''});
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
isServerURLErrored = () => {
|
||||
return this.state.validationResult?.status === URLValidationStatus.Invalid ||
|
||||
this.state.validationResult?.status === URLValidationStatus.Missing;
|
||||
}
|
||||
|
||||
getServerURLMessage = () => {
|
||||
if (this.state.validationStarted) {
|
||||
return (
|
||||
<div>
|
||||
<Spinner
|
||||
className='NewServerModal-validationSpinner'
|
||||
animation='border'
|
||||
size='sm'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.validating'
|
||||
defaultMessage='Validating...'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return window.desktop.modals.pingDomain(serverUrl).
|
||||
then((result: string) => {
|
||||
this.setState({serverUrl: `${result}://${this.state.serverUrl}`});
|
||||
}).
|
||||
catch(() => {
|
||||
console.error(`Could not ping url: ${serverUrl}`);
|
||||
});
|
||||
if (!this.state.validationResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (this.state.validationResult?.status) {
|
||||
case URLValidationStatus.Missing:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlRequired'
|
||||
defaultMessage='URL is required.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.Invalid:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlIncorrectFormatting'
|
||||
defaultMessage='URL is not formatted correctly.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.URLExists:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.serverUrlExists'
|
||||
defaultMessage='A server named {serverName} with the same Site URL already exists.'
|
||||
values={{serverName: this.state.validationResult.existingServerName}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.Insecure:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.insecure'
|
||||
defaultMessage='Your server URL is potentially insecure. For best results, use a URL with the HTTPS protocol.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.NotMattermost:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.notMattermost'
|
||||
defaultMessage='The server URL provided does not appear to point to a valid Mattermost server. Please verify the URL and check your connection.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.URLNotMatched:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.urlNotMatched'
|
||||
defaultMessage='The server URL does not match the configured Site URL on your Mattermost server. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case URLValidationStatus.URLUpdated:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='info'
|
||||
>
|
||||
<i className='icon-information-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.urlUpdated'
|
||||
defaultMessage='The server URL provided has been updated to match the configured Site URL on your Mattermost server. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='success'
|
||||
>
|
||||
<i className='icon-check-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.success.ok'
|
||||
defaultMessage='Server URL is valid. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getError() {
|
||||
const nameError = this.getServerNameValidationError();
|
||||
const urlError = this.getServerUrlValidationError();
|
||||
|
||||
if (nameError && urlError) {
|
||||
getServerNameMessage = () => {
|
||||
if (!this.state.serverName.length) {
|
||||
return (
|
||||
<>
|
||||
{nameError}
|
||||
<br/>
|
||||
{urlError}
|
||||
</>
|
||||
<div
|
||||
id='nameValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.nameRequired'
|
||||
defaultMessage='Name is required.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (nameError) {
|
||||
return nameError;
|
||||
} else if (urlError) {
|
||||
return urlError;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
validateForm() {
|
||||
return this.getServerNameValidationState() === null &&
|
||||
this.getServerUrlValidationState() === null;
|
||||
}
|
||||
save = () => {
|
||||
if (!this.state.validationResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isServerURLErrored()) {
|
||||
return;
|
||||
}
|
||||
|
||||
save = async () => {
|
||||
await this.addProtocolToUrl(this.state.serverUrl);
|
||||
this.setState({
|
||||
saveStarted: true,
|
||||
}, () => {
|
||||
if (this.validateForm()) {
|
||||
this.props.onSave?.({
|
||||
url: this.state.serverUrl,
|
||||
name: this.state.serverName,
|
||||
id: this.state.serverId,
|
||||
});
|
||||
}
|
||||
this.props.onSave?.({
|
||||
url: this.state.serverUrl,
|
||||
name: this.state.serverName,
|
||||
id: this.state.serverId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,7 +383,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
this.props.setInputRef(ref);
|
||||
}
|
||||
}}
|
||||
isInvalid={Boolean(this.getServerUrlValidationState())}
|
||||
isInvalid={this.isServerURLErrored()}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
@@ -318,7 +410,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
isInvalid={Boolean(this.getServerNameValidationState())}
|
||||
isInvalid={!this.state.serverName.length}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
<FormText className='NewServerModal-noBottomSpace'>
|
||||
@@ -329,15 +421,15 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
</FormText>
|
||||
</FormGroup>
|
||||
</form>
|
||||
<div
|
||||
className='NewServerModal-validation'
|
||||
>
|
||||
{this.getServerNameMessage()}
|
||||
{this.getServerURLMessage()}
|
||||
</div>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<div
|
||||
className='pull-left modal-error'
|
||||
>
|
||||
{this.getError()}
|
||||
</div>
|
||||
|
||||
{this.props.onClose &&
|
||||
<Button
|
||||
id='cancelNewServerModal'
|
||||
@@ -354,7 +446,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
<Button
|
||||
id='saveNewServerModal'
|
||||
onClick={this.save}
|
||||
disabled={!this.validateForm()}
|
||||
disabled={!this.state.serverName.length || !this.state.validationResult || this.isServerURLErrored()}
|
||||
variant='primary'
|
||||
>
|
||||
{this.getSaveButtonLabel()}
|
||||
|
Reference in New Issue
Block a user