[MM-63224] Add incompatible server screen (#3348)

* [MM-63224] Add incompatible server screen

* Fixed issue where init isn't called on no server case

* Amend check
This commit is contained in:
Devin Binnie
2025-02-21 10:17:49 -05:00
committed by GitHub
parent 2cf4aaaa02
commit 6fa5508588
17 changed files with 286 additions and 68 deletions

View File

@@ -169,9 +169,13 @@
"renderer.components.developerModeIndicator.tooltip": "Developer mode is enabled. You should only have this enabled if a Mattermost developer has instructed you to.", "renderer.components.developerModeIndicator.tooltip": "Developer mode is enabled. You should only have this enabled if a Mattermost developer has instructed you to.",
"renderer.components.errorView.cannotConnectToThisServer": "Couldn't connect to this server", "renderer.components.errorView.cannotConnectToThisServer": "Couldn't connect to this server",
"renderer.components.errorView.contactAdmin": "If the issue persists, please contact your admin", "renderer.components.errorView.contactAdmin": "If the issue persists, please contact your admin",
"renderer.components.errorView.contactAdminUpgrade": "If the issue persists, contact your {appName} Administrator or IT department to upgrade this {appName} Server.",
"renderer.components.errorView.havingTroubleConnecting": "We're having trouble connecting to this {appName} server. We'll keep trying to establish a connection.", "renderer.components.errorView.havingTroubleConnecting": "We're having trouble connecting to this {appName} server. We'll keep trying to establish a connection.",
"renderer.components.errorView.incompatibleServerVersion": "Incompatible server version",
"renderer.components.errorView.refreshThenVerify": "If refreshing this page (Ctrl+R or Command+R) doesn't help, please check the following:", "renderer.components.errorView.refreshThenVerify": "If refreshing this page (Ctrl+R or Command+R) doesn't help, please check the following:",
"renderer.components.errorView.serverVersionIsIncompatible": "The {appName} Server you are accessing is incompatible with this version of the {appName} Desktop App. To connect to this server, please try the following:",
"renderer.components.errorView.troubleshooting.computerIsConnected": "Ensure your computer is connected to the internet.", "renderer.components.errorView.troubleshooting.computerIsConnected": "Ensure your computer is connected to the internet.",
"renderer.components.errorView.troubleshooting.downgradeApp": "<link>Downgrade your {appName} Desktop App</link> to version v5.10 or earlier.",
"renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "Verify that the URL <link>{url}</link> is correct.", "renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "Verify that the URL <link>{url}</link> is correct.",
"renderer.components.errorView.troubleshooting.webContentsView.canReachFromBrowserWindow": "Try opening <link>{url}</link> in a browser window.", "renderer.components.errorView.troubleshooting.webContentsView.canReachFromBrowserWindow": "Try opening <link>{url}</link> in a browser window.",
"renderer.components.input.required": "This field is required", "renderer.components.input.required": "This field is required",

View File

@@ -59,6 +59,11 @@ export class ServerViewState {
} }
init = () => { init = () => {
// Don't need to init twice
if (this.currentServerId) {
return;
}
const orderedServers = ServerManager.getOrderedServers(); const orderedServers = ServerManager.getOrderedServers();
if (orderedServers.length) { if (orderedServers.length) {
if (Config.lastActiveServer && orderedServers[Config.lastActiveServer]) { if (Config.lastActiveServer && orderedServers[Config.lastActiveServer]) {

View File

@@ -30,6 +30,7 @@ export const APP_MENU_WILL_CLOSE = 'app-menu-will-close';
export const LOAD_RETRY = 'load_retry'; export const LOAD_RETRY = 'load_retry';
export const LOAD_SUCCESS = 'load_success'; export const LOAD_SUCCESS = 'load_success';
export const LOAD_FAILED = 'load_fail'; export const LOAD_FAILED = 'load_fail';
export const LOAD_INCOMPATIBLE_SERVER = 'load_incompatible_server';
export const MAXIMIZE_CHANGE = 'maximized_change'; export const MAXIMIZE_CHANGE = 'maximized_change';
@@ -102,6 +103,7 @@ export const UPDATE_PATHS = 'update-paths';
export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width'; export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width';
export const OPEN_SERVER_EXTERNALLY = 'open-server-externally'; export const OPEN_SERVER_EXTERNALLY = 'open-server-externally';
export const OPEN_SERVER_UPGRADE_LINK = 'open-server-upgrade-link';
export const PING_DOMAIN = 'ping-domain'; export const PING_DOMAIN = 'ping-domain';

View File

@@ -4,7 +4,7 @@
import type {BuildConfig} from 'types/config'; import type {BuildConfig} from 'types/config';
import {DEFAULT_ACADEMY_LINK, DEFAULT_HELP_LINK} from '../../common/constants'; import {DEFAULT_ACADEMY_LINK, DEFAULT_HELP_LINK, DEFAULT_UPGRADE_LINK} from '../../common/constants';
// For detailed guides, please refer to https://docs.mattermost.com/deployment/desktop-app-deployment.html // For detailed guides, please refer to https://docs.mattermost.com/deployment/desktop-app-deployment.html
@@ -31,6 +31,7 @@ const buildConfig: BuildConfig = {
*/], */],
helpLink: DEFAULT_HELP_LINK, helpLink: DEFAULT_HELP_LINK,
academyLink: DEFAULT_ACADEMY_LINK, academyLink: DEFAULT_ACADEMY_LINK,
upgradeLink: DEFAULT_UPGRADE_LINK,
enableServerManagement: true, enableServerManagement: true,
enableAutoUpdater: true, enableAutoUpdater: true,
managedResources: ['trusted'], managedResources: ['trusted'],

View File

@@ -219,6 +219,9 @@ export class Config extends EventEmitter {
get academyLink() { get academyLink() {
return this.combinedData?.academyLink; return this.combinedData?.academyLink;
} }
get upgradeLink() {
return this.combinedData?.upgradeLink;
}
get minimizeToTray() { get minimizeToTray() {
return this.combinedData?.minimizeToTray; return this.combinedData?.minimizeToTray;
} }

View File

@@ -48,3 +48,4 @@ export const DEFAULT_HELP_LINK = 'https://docs.mattermost.com/guides/collaborate
export const DEFAULT_ACADEMY_LINK = 'https://academy.mattermost.com/'; export const DEFAULT_ACADEMY_LINK = 'https://academy.mattermost.com/';
export const DEFAULT_TE_REPORT_PROBLEM_LINK = 'https://mattermost.com/pl/report-a-bug'; export const DEFAULT_TE_REPORT_PROBLEM_LINK = 'https://mattermost.com/pl/report-a-bug';
export const DEFAULT_EE_REPORT_PROBLEM_LINK = 'https://support.mattermost.com/hc/en-us/requests/new'; export const DEFAULT_EE_REPORT_PROBLEM_LINK = 'https://support.mattermost.com/hc/en-us/requests/new';
export const DEFAULT_UPGRADE_LINK = 'https://forum.mattermost.com/t/mattermost-desktop-app-5-11-important-compatibility-notice/22599';

View File

@@ -308,7 +308,6 @@ async function initializeAfterAppReady() {
}); });
ServerManager.reloadFromConfig(); ServerManager.reloadFromConfig();
updateServerInfos(ServerManager.getAllServers());
ServerManager.on(SERVERS_URL_MODIFIED, (serverIds?: string[]) => { ServerManager.on(SERVERS_URL_MODIFIED, (serverIds?: string[]) => {
if (serverIds && serverIds.length) { if (serverIds && serverIds.length) {
updateServerInfos(serverIds.map((srvId) => ServerManager.getServer(srvId)!)); updateServerInfos(serverIds.map((srvId) => ServerManager.getServer(srvId)!));

View File

@@ -93,6 +93,8 @@ import {
IS_DEVELOPER_MODE_ENABLED, IS_DEVELOPER_MODE_ENABLED,
METRICS_REQUEST, METRICS_REQUEST,
METRICS_RECEIVE, METRICS_RECEIVE,
LOAD_INCOMPATIBLE_SERVER,
OPEN_SERVER_UPGRADE_LINK,
} from 'common/communication'; } from 'common/communication';
console.log('Preload initialized'); console.log('Preload initialized');
@@ -120,6 +122,7 @@ contextBridge.exposeInMainWorld('desktop', {
doubleClickOnWindow: (windowName) => ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, windowName), doubleClickOnWindow: (windowName) => ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, windowName),
focusCurrentView: () => ipcRenderer.send(FOCUS_BROWSERVIEW), focusCurrentView: () => ipcRenderer.send(FOCUS_BROWSERVIEW),
openServerExternally: () => ipcRenderer.send(OPEN_SERVER_EXTERNALLY), openServerExternally: () => ipcRenderer.send(OPEN_SERVER_EXTERNALLY),
openServerUpgradeLink: () => ipcRenderer.send(OPEN_SERVER_UPGRADE_LINK),
closeDownloadsDropdown: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN), closeDownloadsDropdown: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN),
closeDownloadsDropdownMenu: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN_MENU), closeDownloadsDropdownMenu: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN_MENU),
openDownloadsDropdown: () => ipcRenderer.send(OPEN_DOWNLOADS_DROPDOWN), openDownloadsDropdown: () => ipcRenderer.send(OPEN_DOWNLOADS_DROPDOWN),
@@ -154,6 +157,7 @@ contextBridge.exposeInMainWorld('desktop', {
onLoadRetry: (listener) => ipcRenderer.on(LOAD_RETRY, (_, viewId, retry, err, loadUrl) => listener(viewId, retry, err, loadUrl)), onLoadRetry: (listener) => ipcRenderer.on(LOAD_RETRY, (_, viewId, retry, err, loadUrl) => listener(viewId, retry, err, loadUrl)),
onLoadSuccess: (listener) => ipcRenderer.on(LOAD_SUCCESS, (_, viewId) => listener(viewId)), onLoadSuccess: (listener) => ipcRenderer.on(LOAD_SUCCESS, (_, viewId) => listener(viewId)),
onLoadFailed: (listener) => ipcRenderer.on(LOAD_FAILED, (_, viewId, err, loadUrl) => listener(viewId, err, loadUrl)), onLoadFailed: (listener) => ipcRenderer.on(LOAD_FAILED, (_, viewId, err, loadUrl) => listener(viewId, err, loadUrl)),
onLoadIncompatibleServer: (listener) => ipcRenderer.on(LOAD_INCOMPATIBLE_SERVER, (_, viewId, loadUrl) => listener(viewId, loadUrl)),
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, viewId) => listener(serverId, viewId)), onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, viewId) => listener(serverId, viewId)),
onMaximizeChange: (listener) => ipcRenderer.on(MAXIMIZE_CHANGE, (_, maximize) => listener(maximize)), onMaximizeChange: (listener) => ipcRenderer.on(MAXIMIZE_CHANGE, (_, maximize) => listener(maximize)),
onEnterFullScreen: (listener) => ipcRenderer.on('enter-full-screen', () => listener()), onEnterFullScreen: (listener) => ipcRenderer.on('enter-full-screen', () => listener()),

View File

@@ -6,6 +6,7 @@
import AppState from 'common/appState'; import AppState from 'common/appState';
import {LOAD_FAILED, UPDATE_TARGET_URL} from 'common/communication'; import {LOAD_FAILED, UPDATE_TARGET_URL} from 'common/communication';
import {MattermostServer} from 'common/servers/MattermostServer'; import {MattermostServer} from 'common/servers/MattermostServer';
import ServerManager from 'common/servers/serverManager';
import MessagingView from 'common/views/MessagingView'; import MessagingView from 'common/views/MessagingView';
import {MattermostWebContentsView} from './MattermostWebContentsView'; import {MattermostWebContentsView} from './MattermostWebContentsView';
@@ -68,6 +69,16 @@ jest.mock('main/performanceMonitor', () => ({
registerServerView: jest.fn(), registerServerView: jest.fn(),
unregisterView: jest.fn(), unregisterView: jest.fn(),
})); }));
jest.mock('common/servers/serverManager', () => ({
getRemoteInfo: jest.fn(),
getViewLog: jest.fn().mockReturnValue({
verbose: jest.fn(),
info: jest.fn(),
error: jest.fn(),
silly: jest.fn(),
}),
on: jest.fn(),
}));
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'}); const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
const view = new MessagingView(server, true); const view = new MessagingView(server, true);
@@ -268,6 +279,7 @@ describe('main/views/MattermostWebContentsView', () => {
mattermostView.setInitialized = jest.fn(); mattermostView.setInitialized = jest.fn();
mattermostView.updateMentionsFromTitle = jest.fn(); mattermostView.updateMentionsFromTitle = jest.fn();
mattermostView.findUnreadState = jest.fn(); mattermostView.findUnreadState = jest.fn();
ServerManager.getRemoteInfo.mockReturnValue({serverVersion: '10.0.0'});
}); });
afterAll(() => { afterAll(() => {

View File

@@ -4,6 +4,7 @@
import {WebContentsView, app, ipcMain} from 'electron'; import {WebContentsView, app, ipcMain} from 'electron';
import type {WebContentsViewConstructorOptions, Event, Input} from 'electron/main'; import type {WebContentsViewConstructorOptions, Event, Input} from 'electron/main';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import semver from 'semver';
import AppState from 'common/appState'; import AppState from 'common/appState';
import { import {
@@ -16,6 +17,7 @@ import {
BROWSER_HISTORY_STATUS_UPDATED, BROWSER_HISTORY_STATUS_UPDATED,
CLOSE_SERVERS_DROPDOWN, CLOSE_SERVERS_DROPDOWN,
CLOSE_DOWNLOADS_DROPDOWN, CLOSE_DOWNLOADS_DROPDOWN,
LOAD_INCOMPATIBLE_SERVER,
} from 'common/communication'; } from 'common/communication';
import type {Logger} from 'common/log'; import type {Logger} from 'common/log';
import ServerManager from 'common/servers/serverManager'; import ServerManager from 'common/servers/serverManager';
@@ -426,15 +428,22 @@ export class MattermostWebContentsView extends EventEmitter {
private loadSuccess = (loadURL: string) => { private loadSuccess = (loadURL: string) => {
return () => { return () => {
this.log.verbose(`finished loading ${loadURL}`); const serverInfo = ServerManager.getRemoteInfo(this.view.server.id);
MainWindow.sendToRenderer(LOAD_SUCCESS, this.id); if (serverInfo?.serverVersion && semver.gte(serverInfo.serverVersion, '9.4.0')) {
this.maxRetries = MAX_SERVER_RETRIES; this.log.verbose(`finished loading ${loadURL}`);
this.status = Status.WAITING_MM; MainWindow.sendToRenderer(LOAD_SUCCESS, this.id);
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true); this.maxRetries = MAX_SERVER_RETRIES;
this.emit(LOAD_SUCCESS, this.id, loadURL); this.status = Status.WAITING_MM;
const mainWindow = MainWindow.get(); this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
if (mainWindow && this.currentURL) { this.emit(LOAD_SUCCESS, this.id, loadURL);
this.setBounds(getWindowBoundaries(mainWindow)); const mainWindow = MainWindow.get();
if (mainWindow && this.currentURL) {
this.setBounds(getWindowBoundaries(mainWindow));
}
} else {
MainWindow.sendToRenderer(LOAD_INCOMPATIBLE_SERVER, this.id, loadURL.toString());
this.emit(LOAD_FAILED, this.id, 'Incompatible server version', loadURL.toString());
this.status = Status.ERROR;
} }
}; };
}; };

View File

@@ -31,6 +31,7 @@ import {
UNREADS_AND_MENTIONS, UNREADS_AND_MENTIONS,
TAB_LOGIN_CHANGED, TAB_LOGIN_CHANGED,
DEVELOPER_MODE_UPDATED, DEVELOPER_MODE_UPDATED,
OPEN_SERVER_UPGRADE_LINK,
} from 'common/communication'; } from 'common/communication';
import Config from 'common/config'; import Config from 'common/config';
import {Logger} from 'common/log'; import {Logger} from 'common/log';
@@ -42,7 +43,7 @@ import Utils from 'common/utils/util';
import type {MattermostView} from 'common/views/View'; import type {MattermostView} from 'common/views/View';
import {TAB_MESSAGING} from 'common/views/View'; import {TAB_MESSAGING} from 'common/views/View';
import {handleWelcomeScreenModal} from 'main/app/intercom'; import {handleWelcomeScreenModal} from 'main/app/intercom';
import {flushCookiesStore} from 'main/app/utils'; import {flushCookiesStore, updateServerInfos} from 'main/app/utils';
import DeveloperMode from 'main/developerMode'; import DeveloperMode from 'main/developerMode';
import performanceMonitor from 'main/performanceMonitor'; import performanceMonitor from 'main/performanceMonitor';
import PermissionsManager from 'main/permissionsManager'; import PermissionsManager from 'main/permissionsManager';
@@ -82,6 +83,7 @@ export class ViewManager {
ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush); ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush);
ipcMain.on(TAB_LOGIN_CHANGED, this.handleTabLoginChanged); ipcMain.on(TAB_LOGIN_CHANGED, this.handleTabLoginChanged);
ipcMain.on(OPEN_SERVER_EXTERNALLY, this.handleOpenServerExternally); ipcMain.on(OPEN_SERVER_EXTERNALLY, this.handleOpenServerExternally);
ipcMain.on(OPEN_SERVER_UPGRADE_LINK, this.handleOpenServerUpgradeLink);
ipcMain.on(UNREADS_AND_MENTIONS, this.handleUnreadsAndMentionsChanged); ipcMain.on(UNREADS_AND_MENTIONS, this.handleUnreadsAndMentionsChanged);
ipcMain.on(SESSION_EXPIRED, this.handleSessionExpired); ipcMain.on(SESSION_EXPIRED, this.handleSessionExpired);
@@ -91,8 +93,11 @@ export class ViewManager {
DeveloperMode.on(DEVELOPER_MODE_UPDATED, this.handleDeveloperModeUpdated); DeveloperMode.on(DEVELOPER_MODE_UPDATED, this.handleDeveloperModeUpdated);
} }
private init = () => { private init = async () => {
if (ServerManager.hasServers()) { if (ServerManager.hasServers()) {
// TODO: This init should be happening elsewhere, future refactor will fix this
ServerViewState.init();
await updateServerInfos(ServerManager.getAllServers());
LoadingScreen.show(); LoadingScreen.show();
ServerManager.getAllServers().forEach((server) => this.loadServer(server)); ServerManager.getAllServers().forEach((server) => this.loadServer(server));
this.showInitial(); this.showInitial();
@@ -303,7 +308,6 @@ export class ViewManager {
private showInitial = () => { private showInitial = () => {
log.verbose('showInitial'); log.verbose('showInitial');
// TODO: This init should be happening elsewhere, future refactor will fix this
ServerViewState.init(); ServerViewState.init();
if (ServerManager.hasServers()) { if (ServerManager.hasServers()) {
const lastActiveServer = ServerViewState.getCurrentServer(); const lastActiveServer = ServerViewState.getCurrentServer();
@@ -577,6 +581,12 @@ export class ViewManager {
shell.openExternal(view.view.server.url.toString()); shell.openExternal(view.view.server.url.toString());
}; };
private handleOpenServerUpgradeLink = () => {
if (Config.upgradeLink) {
shell.openExternal(Config.upgradeLink);
}
};
private handleUnreadsAndMentionsChanged = (e: IpcMainEvent, isUnread: boolean, mentionCount: number) => { private handleUnreadsAndMentionsChanged = (e: IpcMainEvent, isUnread: boolean, mentionCount: number) => {
log.silly('handleUnreadsAndMentionsChanged', {webContentsId: e.sender.id, isUnread, mentionCount}); log.silly('handleUnreadsAndMentionsChanged', {webContentsId: e.sender.id, isUnread, mentionCount});

View File

@@ -0,0 +1,90 @@
// 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 ErrorView from './ErrorView';
type Props = {
darkMode: boolean;
appName?: string;
url?: string;
errorInfo?: string;
handleLink: () => void;
};
export default function ConnectionErrorView({darkMode, appName, url, handleLink, errorInfo}: Props) {
const header = (
<FormattedMessage
id='renderer.components.errorView.cannotConnectToThisServer'
defaultMessage="Couldn't connect to this server"
/>
);
const subHeader = (
<>
<FormattedMessage
id='renderer.components.errorView.havingTroubleConnecting'
defaultMessage={'We\'re having trouble connecting to this {appName} server. We\'ll keep trying to establish a connection.'}
values={{
appName,
}}
/>
<br/>
<FormattedMessage
id='renderer.components.errorView.refreshThenVerify'
defaultMessage="If refreshing this page (Ctrl+R or Command+R) doesn't help, please check the following:"
/>
</>
);
const bullets = (
<>
<li>
<FormattedMessage
id='renderer.components.errorView.troubleshooting.computerIsConnected'
defaultMessage='Ensure your computer is connected to the internet.'
/>
</li>
<li>
<FormattedMessage
id='renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect'
defaultMessage='Verify that the URL <link>{url}</link> is correct.'
values={{
appName,
url,
link: (msg: React.ReactNode) => (
<a
onClick={handleLink}
href='#'
>
{msg}
</a>
),
}}
/>
</li>
</>
);
const contactAdmin = (
<FormattedMessage
id='renderer.components.errorView.contactAdmin'
defaultMessage='If the issue persists, please contact your admin'
/>
);
return (
<ErrorView
darkMode={darkMode}
header={header}
subHeader={subHeader}
bullets={bullets}
contactAdmin={contactAdmin}
handleLink={handleLink}
errorInfo={errorInfo}
url={url}
/>
);
}

View File

@@ -12,72 +12,47 @@ import AlertImage from './Images/alert';
import 'renderer/css/components/ErrorView.scss'; import 'renderer/css/components/ErrorView.scss';
type Props = { type ErrorViewProps = {
darkMode: boolean; darkMode: boolean;
header: React.ReactNode;
subHeader: React.ReactNode;
bullets: React.ReactNode;
contactAdmin: React.ReactNode;
errorInfo?: string; errorInfo?: string;
url?: string; url?: string;
appName?: string;
handleLink: () => void; handleLink: () => void;
}; };
export default function ErrorView(props: Props) { export default function ErrorView({
darkMode,
header,
subHeader,
bullets,
contactAdmin,
errorInfo,
url,
handleLink,
}: ErrorViewProps) {
return ( return (
<div className={classNames('ErrorView', {darkMode: props.darkMode})}> <div className={classNames('ErrorView', {darkMode})}>
<AlertImage/> <AlertImage/>
<span className='ErrorView-header'> <span className='ErrorView-header'>
<FormattedMessage {header}
id='renderer.components.errorView.cannotConnectToThisServer'
defaultMessage="Couldn't connect to this server"
/>
</span> </span>
<span> <span>
<FormattedMessage {subHeader}
id='renderer.components.errorView.havingTroubleConnecting'
defaultMessage={'We\'re having trouble connecting to this {appName} server. We\'ll keep trying to establish a connection.'}
values={{
appName: props.appName,
}}
/>
<br/>
<FormattedMessage
id='renderer.components.errorView.refreshThenVerify'
defaultMessage="If refreshing this page (Ctrl+R or Command+R) doesn't help, please check the following:"
/>
</span> </span>
<ul className='ErrorView-bullets'> <ul className='ErrorView-bullets'>
<li> {bullets}
<FormattedMessage
id='renderer.components.errorView.troubleshooting.computerIsConnected'
defaultMessage='Ensure your computer is connected to the internet.'
/>
</li>
<li>
<FormattedMessage
id='renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect'
defaultMessage='Verify that the URL <link>{url}</link> is correct.'
values={{
appName: props.appName,
url: props.url,
link: (msg: React.ReactNode) => (
<a
onClick={props.handleLink}
href='#'
>
{msg}
</a>
),
}}
/>
</li>
<li> <li>
<FormattedMessage <FormattedMessage
id='renderer.components.errorView.troubleshooting.webContentsView.canReachFromBrowserWindow' id='renderer.components.errorView.troubleshooting.webContentsView.canReachFromBrowserWindow'
defaultMessage='Try opening <link>{url}</link> in a browser window.' defaultMessage='Try opening <link>{url}</link> in a browser window.'
values={{ values={{
url: props.url, url,
link: (msg: React.ReactNode) => ( link: (msg: React.ReactNode) => (
<a <a
onClick={props.handleLink} onClick={handleLink}
href='#' href='#'
> >
{msg} {msg}
@@ -88,13 +63,10 @@ export default function ErrorView(props: Props) {
</li> </li>
</ul> </ul>
<span> <span>
<FormattedMessage {contactAdmin}
id='renderer.components.errorView.contactAdmin'
defaultMessage='If the issue persists, please contact your admin'
/>
</span> </span>
<span className='ErrorView-techInfo'> <span className='ErrorView-techInfo'>
{props.errorInfo} {errorInfo}
</span> </span>
</div> </div>
); );

View File

@@ -0,0 +1,80 @@
// 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 ErrorView from './ErrorView';
type Props = {
darkMode: boolean;
appName?: string;
url?: string;
handleLink: () => void;
handleUpgradeLink: () => void;
};
export default function IncompatibleErrorView({darkMode, appName, url, handleLink, handleUpgradeLink}: Props) {
const header = (
<FormattedMessage
id='renderer.components.errorView.incompatibleServerVersion'
defaultMessage='Incompatible server version'
/>
);
const subHeader = (
<>
<FormattedMessage
id='renderer.components.errorView.serverVersionIsIncompatible'
defaultMessage={'The {appName} Server you are accessing is incompatible with this version of the {appName} Desktop App. To connect to this server, please try the following:'}
values={{
appName,
}}
/>
</>
);
const bullets = (
<>
<li>
<FormattedMessage
id='renderer.components.errorView.troubleshooting.downgradeApp'
defaultMessage='<link>Downgrade your {appName} Desktop App</link> to version v5.10 or earlier.'
values={{
appName,
link: (msg: React.ReactNode) => (
<a
href='#'
onClick={handleUpgradeLink}
>
{msg}
</a>
),
}}
/>
</li>
</>
);
const contactAdmin = (
<FormattedMessage
id='renderer.components.errorView.contactAdminUpgrade'
defaultMessage='If the issue persists, contact your {appName} Administrator or IT department to upgrade this {appName} Server.'
values={{
appName,
}}
/>
);
return (
<ErrorView
darkMode={darkMode}
header={header}
subHeader={subHeader}
bullets={bullets}
contactAdmin={contactAdmin}
handleLink={handleLink}
url={url}
/>
);
}

View File

@@ -11,9 +11,10 @@ import {injectIntl} from 'react-intl';
import type {UniqueView, UniqueServer} from 'types/config'; import type {UniqueView, UniqueServer} from 'types/config';
import type {DownloadedItems} from 'types/downloads'; import type {DownloadedItems} from 'types/downloads';
import ConnectionErrorView from './ConnectionErrorView';
import DeveloperModeIndicator from './DeveloperModeIndicator'; import DeveloperModeIndicator from './DeveloperModeIndicator';
import DownloadsDropdownButton from './DownloadsDropdown/DownloadsDropdownButton'; import DownloadsDropdownButton from './DownloadsDropdown/DownloadsDropdownButton';
import ErrorView from './ErrorView'; import IncompatibleErrorView from './IncompatibleErrorView';
import ServerDropdownButton from './ServerDropdownButton'; import ServerDropdownButton from './ServerDropdownButton';
import TabBar from './TabBar'; import TabBar from './TabBar';
@@ -28,6 +29,7 @@ enum Status {
RETRY = -1, RETRY = -1,
FAILED = 0, FAILED = 0,
NOSERVERS = -2, NOSERVERS = -2,
INCOMPATIBLE = -3,
} }
type Props = { type Props = {
@@ -61,7 +63,7 @@ type TabViewStatus = {
status: Status; status: Status;
extra?: { extra?: {
url: string; url: string;
error: string; error?: string;
}; };
} }
@@ -186,6 +188,17 @@ class MainPage extends React.PureComponent<Props, State> {
this.updateTabStatus(viewId, statusValue); this.updateTabStatus(viewId, statusValue);
}); });
window.desktop.onLoadIncompatibleServer((viewId, loadUrl) => {
console.error(`${viewId}: tried to load incompatible server`);
const statusValue = {
status: Status.INCOMPATIBLE,
extra: {
url: loadUrl,
},
};
this.updateTabStatus(viewId, statusValue);
});
// can't switch tabs sequentially for some reason... // can't switch tabs sequentially for some reason...
window.desktop.onSetActiveView(this.setActiveView); window.desktop.onSetActiveView(this.setActiveView);
@@ -507,7 +520,7 @@ class MainPage extends React.PureComponent<Props, State> {
switch (tabStatus.status) { switch (tabStatus.status) {
case Status.FAILED: case Status.FAILED:
component = ( component = (
<ErrorView <ConnectionErrorView
darkMode={this.props.darkMode} darkMode={this.props.darkMode}
errorInfo={tabStatus.extra?.error} errorInfo={tabStatus.extra?.error}
url={tabStatus.extra ? tabStatus.extra.url : ''} url={tabStatus.extra ? tabStatus.extra.url : ''}
@@ -515,6 +528,16 @@ class MainPage extends React.PureComponent<Props, State> {
handleLink={this.openServerExternally} handleLink={this.openServerExternally}
/>); />);
break; break;
case Status.INCOMPATIBLE:
component = (
<IncompatibleErrorView
darkMode={this.props.darkMode}
url={tabStatus.extra ? tabStatus.extra.url : ''}
appName={this.props.appName}
handleLink={this.openServerExternally}
handleUpgradeLink={() => window.desktop.openServerUpgradeLink()}
/>);
break;
case Status.LOADING: case Status.LOADING:
case Status.RETRY: case Status.RETRY:
case Status.DONE: case Status.DONE:

View File

@@ -107,6 +107,7 @@ export type BuildConfig = {
defaultServers?: Server[]; defaultServers?: Server[];
helpLink: string; helpLink: string;
academyLink: string; academyLink: string;
upgradeLink: string;
enableServerManagement: boolean; enableServerManagement: boolean;
enableAutoUpdater: boolean; enableAutoUpdater: boolean;
managedResources: string[]; managedResources: string[];

View File

@@ -38,6 +38,7 @@ declare global {
doubleClickOnWindow: (windowName?: string) => void; doubleClickOnWindow: (windowName?: string) => void;
focusCurrentView: () => void; focusCurrentView: () => void;
openServerExternally: () => void; openServerExternally: () => void;
openServerUpgradeLink: () => void;
closeDownloadsDropdown: () => void; closeDownloadsDropdown: () => void;
closeDownloadsDropdownMenu: () => void; closeDownloadsDropdownMenu: () => void;
openDownloadsDropdown: () => void; openDownloadsDropdown: () => void;
@@ -72,6 +73,7 @@ declare global {
onLoadRetry: (listener: (viewId: string, retry: Date, err: string, loadUrl: string) => void) => void; onLoadRetry: (listener: (viewId: string, retry: Date, err: string, loadUrl: string) => void) => void;
onLoadSuccess: (listener: (viewId: string) => void) => void; onLoadSuccess: (listener: (viewId: string) => void) => void;
onLoadFailed: (listener: (viewId: string, err: string, loadUrl: string) => void) => void; onLoadFailed: (listener: (viewId: string, err: string, loadUrl: string) => void) => void;
onLoadIncompatibleServer: (listener: (viewId: string, loadUrl: string) => void) => void;
onSetActiveView: (listener: (serverId: string, viewId: string) => void) => void; onSetActiveView: (listener: (serverId: string, viewId: string) => void) => void;
onMaximizeChange: (listener: (maximize: boolean) => void) => void; onMaximizeChange: (listener: (maximize: boolean) => void) => void;
onEnterFullScreen: (listener: () => void) => void; onEnterFullScreen: (listener: () => void) => void;