[MM-18552] Autoupdater (#1714)
* wip * background download * various fixes * wip * wokring autoupgrade * fix menu * fix windows * cleanup * add publishername * fix messages and titles * Test updates * Moved module and added functionality to click icon to install (instead of just download) * Add auto update setting, update on close app if downloaded * Tests, changes for security fixes, update version number * Update E2E tests * Lint fix * Update to latest electron-updater * Revert to stable electron-builder (only needed to update electron-updater) * Fix package-lock * skip flaky test * Update package * Fix E2E test * Fixes for enabling/disabled autoupdater * Fixed GPO definitions * [MM-38300] Set localhost as the test server * blank * Switch to s3 bucket for testing * Update icons to match spec * Add menu items for download/update actions * Type and test fixes * Fix notification circle * Fix macOS app not restarting on Restart/Update * Update dialog box titles * Turn off file system check for Linux * Changes to support deployments * Testing autoupdater deployments to s3 * disable tests for now * asfrehwf * fine no windows WHATEVER * remove windows again * Try universal all in one * pffftttngggguhhhh * make sure it's working * Missed artifacts script * Modify destination as well * one more time! * Update yml files * Oops * add yq manually * oof * Fix the script to work properly * Fix release script * Fix script again so it runs in time * Build version 2 * Revert build specific changes * Lint override * Fix build apps for PR builds * One more change * Add file generation for .deb repo * Deb repo test * skip tests for now * Fix artifact push * Persist after repo creation * Put tests back * Fix unit tests * Enable mac generated builds temp * Temporarily disable tests * Fix issue where notification doesn't pop dialog box * Try version 2 again * Put the version back * Attempting to debug mac app path issue * Fix issue where Mac app will quarantine itself after first update * Lock versions of yq * Fix yq for mac * As usual, Mac is difficult :P * Add quotes to anti-quarantine command * Change to spawn to avoid command injection * Oops * Nightly deployment changes (#2005) * Test nightly deploy * I fixed a some things * aaaaaaaaa * Restore old bucket * Added progress indicator via tooltip * Ship nightly builds to main S3 bucket * PR feedback * Fix a couple security exploits * Fix opacity on light mode button * Use large app icon * Resize icon for Windows * Resize icon for Mac * Update to electron-updater final * Remove Mac support and deb repo * Typo * Remove deb script * Remove checksum function * Removed autoUpdateSettingsPath * Update URL Co-authored-by: = <=> Co-authored-by: Devin Binnie <devin.binnie@mattermost.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>
This commit is contained in:
@@ -2,11 +2,13 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {Fragment} from 'react';
|
||||
import {Container, Row} from 'react-bootstrap';
|
||||
import {DropResult} from 'react-beautiful-dnd';
|
||||
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
|
||||
import {IpcRendererEvent} from 'electron/renderer';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import {TeamWithTabs} from 'types/config';
|
||||
|
||||
@@ -36,6 +38,11 @@ import {
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
OPEN_TEAMS_DROPDOWN,
|
||||
SWITCH_TAB,
|
||||
UPDATE_AVAILABLE,
|
||||
UPDATE_DOWNLOADED,
|
||||
UPDATE_PROGRESS,
|
||||
START_UPGRADE,
|
||||
START_DOWNLOAD,
|
||||
CLOSE_TAB,
|
||||
} from 'common/communication';
|
||||
|
||||
@@ -50,6 +57,7 @@ import TabBar from './TabBar';
|
||||
import ExtraBar from './ExtraBar';
|
||||
import ErrorView from './ErrorView';
|
||||
import TeamDropdownButton from './TeamDropdownButton';
|
||||
import '../css/components/UpgradeButton.scss';
|
||||
|
||||
enum Status {
|
||||
LOADING = 1,
|
||||
@@ -59,6 +67,13 @@ enum Status {
|
||||
NOSERVERS = -2,
|
||||
}
|
||||
|
||||
enum UpgradeStatus {
|
||||
NONE = 0,
|
||||
AVAILABLE = 1,
|
||||
DOWNLOADING = 2,
|
||||
DOWNLOADED = 3,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
teams: TeamWithTabs[];
|
||||
lastActiveTeam?: number;
|
||||
@@ -82,6 +97,14 @@ type State = {
|
||||
fullScreen?: boolean;
|
||||
showExtraBar?: boolean;
|
||||
isMenuOpen: boolean;
|
||||
upgradeStatus: UpgradeStatus;
|
||||
upgradeProgress?: {
|
||||
total: number;
|
||||
delta: number;
|
||||
transferred: number;
|
||||
percent: number;
|
||||
bytesPerSecond: number;
|
||||
};
|
||||
};
|
||||
|
||||
type TabViewStatus = {
|
||||
@@ -119,6 +142,7 @@ export default class MainPage extends React.PureComponent<Props, State> {
|
||||
tabViewStatus: new Map(this.props.teams.map((team) => team.tabs.map((tab) => getTabViewName(team.name, tab.name))).flat().map((tabViewName) => [tabViewName, {status: Status.LOADING}])),
|
||||
darkMode: this.props.darkMode,
|
||||
isMenuOpen: false,
|
||||
upgradeStatus: UpgradeStatus.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -221,6 +245,27 @@ export default class MainPage extends React.PureComponent<Props, State> {
|
||||
this.setState({isMenuOpen: true});
|
||||
});
|
||||
|
||||
window.ipcRenderer.on(UPDATE_AVAILABLE, () => {
|
||||
this.setState({upgradeStatus: UpgradeStatus.AVAILABLE});
|
||||
});
|
||||
|
||||
window.ipcRenderer.on(UPDATE_DOWNLOADED, () => {
|
||||
this.setState({upgradeStatus: UpgradeStatus.DOWNLOADED});
|
||||
});
|
||||
|
||||
window.ipcRenderer.on(UPDATE_PROGRESS, (event, total, delta, transferred, percent, bytesPerSecond) => {
|
||||
this.setState({
|
||||
upgradeStatus: UpgradeStatus.DOWNLOADING,
|
||||
upgradeProgress: {
|
||||
total,
|
||||
delta,
|
||||
transferred,
|
||||
percent,
|
||||
bytesPerSecond,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (window.process.platform !== 'darwin') {
|
||||
window.ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => {
|
||||
if (this.threeDotMenu.current) {
|
||||
@@ -334,16 +379,11 @@ export default class MainPage extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
|
||||
let topBarClassName = 'topBar';
|
||||
if (window.process.platform === 'darwin') {
|
||||
topBarClassName += ' macOS';
|
||||
}
|
||||
if (this.state.darkMode) {
|
||||
topBarClassName += ' darkMode';
|
||||
}
|
||||
if (this.state.fullScreen) {
|
||||
topBarClassName += ' fullScreen';
|
||||
}
|
||||
const topBarClassName = classNames('topBar', {
|
||||
macOS: window.process.platform === 'darwin',
|
||||
darkMode: this.state.darkMode,
|
||||
fullScreen: this.state.fullScreen,
|
||||
});
|
||||
|
||||
let maxButton;
|
||||
if (this.state.maximized || this.state.fullScreen) {
|
||||
@@ -366,11 +406,46 @@ export default class MainPage extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
let overlayGradient;
|
||||
if (window.process.platform !== 'darwin') {
|
||||
overlayGradient = (
|
||||
<span className='overlay-gradient'/>
|
||||
);
|
||||
let upgradeTooltip;
|
||||
switch (this.state.upgradeStatus) {
|
||||
case UpgradeStatus.AVAILABLE:
|
||||
upgradeTooltip = 'Update available';
|
||||
break;
|
||||
case UpgradeStatus.DOWNLOADED:
|
||||
upgradeTooltip = 'Update ready to install';
|
||||
break;
|
||||
case UpgradeStatus.DOWNLOADING:
|
||||
upgradeTooltip = `Downloading update. ${String(this.state.upgradeProgress?.percent).split('.')[0]}% of ${prettyBytes(this.state.upgradeProgress?.total || 0)} @ ${prettyBytes(this.state.upgradeProgress?.bytesPerSecond || 0)}/s`;
|
||||
break;
|
||||
}
|
||||
|
||||
let upgradeIcon;
|
||||
if (this.state.upgradeStatus !== UpgradeStatus.NONE) {
|
||||
upgradeIcon = (
|
||||
<span className={classNames('upgrade-btns', {darkMode: this.state.darkMode})}>
|
||||
<div
|
||||
className={classNames('button upgrade-button', {
|
||||
rotate: this.state.upgradeStatus === UpgradeStatus.DOWNLOADING,
|
||||
})}
|
||||
title={upgradeTooltip}
|
||||
onClick={() => {
|
||||
if (this.state.upgradeStatus === UpgradeStatus.DOWNLOADING) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.ipcRenderer.send(this.state.upgradeStatus === UpgradeStatus.DOWNLOADED ? START_UPGRADE : START_DOWNLOAD);
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={classNames({
|
||||
'icon-arrow-down-bold-circle-outline': this.state.upgradeStatus === UpgradeStatus.AVAILABLE,
|
||||
'icon-sync': this.state.upgradeStatus === UpgradeStatus.DOWNLOADING,
|
||||
'icon-arrow-up-bold-circle-outline': this.state.upgradeStatus === UpgradeStatus.DOWNLOADED,
|
||||
})}
|
||||
/>
|
||||
{(this.state.upgradeStatus !== UpgradeStatus.DOWNLOADING) && <div className={'circle'}/>}
|
||||
</div>
|
||||
</span>);
|
||||
}
|
||||
|
||||
let titleBarButtons;
|
||||
@@ -435,7 +510,7 @@ export default class MainPage extends React.PureComponent<Props, State> {
|
||||
darkMode={this.state.darkMode}
|
||||
/>
|
||||
{tabsRow}
|
||||
{overlayGradient}
|
||||
{upgradeIcon}
|
||||
{titleBarButtons}
|
||||
</div>
|
||||
</Row>
|
||||
|
@@ -24,14 +24,15 @@ import {
|
||||
GET_DOWNLOAD_LOCATION,
|
||||
RELOAD_CONFIGURATION,
|
||||
GET_AVAILABLE_SPELL_CHECKER_LANGUAGES,
|
||||
CHECK_FOR_UPDATES,
|
||||
} from 'common/communication';
|
||||
|
||||
import AutoSaveIndicator, {SavingState} from './AutoSaveIndicator';
|
||||
|
||||
const CONFIG_TYPE_SERVERS = 'servers';
|
||||
const CONFIG_TYPE_UPDATES = 'updates';
|
||||
const CONFIG_TYPE_APP_OPTIONS = 'appOptions';
|
||||
|
||||
type ConfigType = typeof CONFIG_TYPE_SERVERS | typeof CONFIG_TYPE_APP_OPTIONS;
|
||||
type ConfigType = typeof CONFIG_TYPE_UPDATES | typeof CONFIG_TYPE_APP_OPTIONS;
|
||||
|
||||
type State = DeepPartial<CombinedConfig> & {
|
||||
ready: boolean;
|
||||
@@ -40,11 +41,12 @@ type State = DeepPartial<CombinedConfig> & {
|
||||
userOpenedDownloadDialog: boolean;
|
||||
allowSaveSpellCheckerURL: boolean;
|
||||
availableLanguages: Array<{label: string; value: string}>;
|
||||
canUpgrade?: boolean;
|
||||
}
|
||||
|
||||
type SavingStateItems = {
|
||||
appOptions: SavingState;
|
||||
servers: SavingState;
|
||||
updates: SavingState;
|
||||
};
|
||||
|
||||
type SaveQueueItem = {
|
||||
@@ -66,6 +68,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
useSpellCheckerRef: React.RefObject<HTMLInputElement>;
|
||||
spellCheckerURLRef: React.RefObject<HTMLInputElement>;
|
||||
enableHardwareAccelerationRef: React.RefObject<HTMLInputElement>;
|
||||
autoCheckForUpdatesRef: React.RefObject<HTMLInputElement>;
|
||||
|
||||
saveQueue: SaveQueueItem[];
|
||||
|
||||
@@ -77,7 +80,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
ready: false,
|
||||
savingState: {
|
||||
appOptions: SavingState.SAVING_STATE_DONE,
|
||||
servers: SavingState.SAVING_STATE_DONE,
|
||||
updates: SavingState.SAVING_STATE_DONE,
|
||||
},
|
||||
userOpenedDownloadDialog: false,
|
||||
allowSaveSpellCheckerURL: false,
|
||||
@@ -97,6 +100,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
this.useSpellCheckerRef = React.createRef();
|
||||
this.enableHardwareAccelerationRef = React.createRef();
|
||||
this.spellCheckerURLRef = React.createRef();
|
||||
this.autoCheckForUpdatesRef = React.createRef();
|
||||
|
||||
this.saveQueue = [];
|
||||
this.selectedSpellCheckerLocales = [];
|
||||
@@ -125,7 +129,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
const newState = Object.assign({} as State, configData);
|
||||
newState.savingState = currentState.savingState || {
|
||||
appOptions: SavingState.SAVING_STATE_DONE,
|
||||
servers: SavingState.SAVING_STATE_DONE,
|
||||
updates: SavingState.SAVING_STATE_DONE,
|
||||
};
|
||||
this.selectedSpellCheckerLocales = configData.spellCheckerLocales?.map((language: string) => ({label: localeTranslations[language] || language, value: language})) || [];
|
||||
return newState;
|
||||
@@ -147,7 +151,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
|
||||
updateSaveState = () => {
|
||||
let queuedUpdateCounts = {
|
||||
[CONFIG_TYPE_SERVERS]: 0,
|
||||
[CONFIG_TYPE_UPDATES]: 0,
|
||||
[CONFIG_TYPE_APP_OPTIONS]: 0,
|
||||
};
|
||||
|
||||
@@ -284,6 +288,21 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
});
|
||||
}
|
||||
|
||||
handleChangeAutoCheckForUpdates = () => {
|
||||
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_UPDATES, {key: 'autoCheckForUpdates', data: this.autoCheckForUpdatesRef.current?.checked});
|
||||
this.setState({
|
||||
autoCheckForUpdates: this.autoCheckForUpdatesRef.current?.checked,
|
||||
}, () => {
|
||||
if (this.state.autoCheckForUpdates) {
|
||||
this.checkForUpdates();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkForUpdates = () => {
|
||||
window.ipcRenderer.send(CHECK_FOR_UPDATES);
|
||||
}
|
||||
|
||||
handleChangeSpellCheckerLocales = (value: OptionsType<{label: string; value: string}>, actionMeta: ActionMeta<{label: string; value: string}>) => {
|
||||
switch (actionMeta.action) {
|
||||
case 'select-option':
|
||||
@@ -407,6 +426,12 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
container: {
|
||||
paddingBottom: '40px',
|
||||
},
|
||||
|
||||
checkForUpdatesButton: {
|
||||
marginBottom: '4px',
|
||||
marginLeft: '16px',
|
||||
marginTop: '8px',
|
||||
},
|
||||
};
|
||||
|
||||
const options = [];
|
||||
@@ -757,7 +782,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<h2 style={settingsPage.sectionHeading}>{'App Options'}</h2>
|
||||
<div className='IndicatorContainer'>
|
||||
<div className='IndicatorContainer appOptionsSaveIndicator'>
|
||||
<AutoSaveIndicator
|
||||
id='appOptionsSaveIndicator'
|
||||
savingState={this.state.savingState.appOptions}
|
||||
@@ -774,9 +799,60 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||
);
|
||||
}
|
||||
|
||||
let updateRow = null;
|
||||
if (this.state.canUpgrade) {
|
||||
updateRow = (
|
||||
<>
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<h2 style={settingsPage.sectionHeading}>{'Updates'}</h2>
|
||||
<div className='IndicatorContainer updatesSaveIndicator'>
|
||||
<AutoSaveIndicator
|
||||
id='updatesSaveIndicator'
|
||||
savingState={this.state.savingState.updates}
|
||||
errorMessage={'Can\'t save your changes. Please try again.'}
|
||||
/>
|
||||
</div>
|
||||
<FormGroup
|
||||
key='inputAutoCheckForUpdates'
|
||||
>
|
||||
<FormCheck>
|
||||
<FormCheck.Input
|
||||
type='checkbox'
|
||||
key='inputAutoCheckForUpdates'
|
||||
id='inputAutoCheckForUpdates'
|
||||
ref={this.autoCheckForUpdatesRef}
|
||||
checked={this.state.autoCheckForUpdates}
|
||||
onChange={this.handleChangeAutoCheckForUpdates}
|
||||
/>
|
||||
{'Automatically check for updates'}
|
||||
<FormText>
|
||||
{'If enabled, updates to the Desktop App will download automatically and you will be notified when ready to install.'}
|
||||
</FormText>
|
||||
</FormCheck>
|
||||
<Button
|
||||
style={settingsPage.checkForUpdatesButton}
|
||||
id='checkForUpdatesNow'
|
||||
onClick={this.checkForUpdates}
|
||||
>
|
||||
<span>{'Check for Updates Now'}</span>
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let waitForIpc;
|
||||
if (this.state.ready) {
|
||||
waitForIpc = optionsRow;
|
||||
waitForIpc = (
|
||||
<>
|
||||
{updateRow}
|
||||
{optionsRow}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
waitForIpc = (<p>{'Loading configuration...'}</p>);
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import React, {useEffect} from 'react';
|
||||
import {CLOSE_TEAMS_DROPDOWN, OPEN_TEAMS_DROPDOWN} from 'common/communication';
|
||||
|
||||
import '../css/components/TeamDropdownButton.scss';
|
||||
import '../css/compass-icons.css';
|
||||
|
||||
type Props = {
|
||||
isDisabled?: boolean;
|
||||
|
Reference in New Issue
Block a user