mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 23:37:18 +00:00
Use rendition modal for warning and errors when flashing
Change-type: patch Signed-off-by: Stevche Radevski <stevche@balena.io>
This commit is contained in:
parent
00536cba3a
commit
21d9d31a27
@ -87,7 +87,6 @@ const app = angular.module('Etcher', [
|
||||
|
||||
// Components
|
||||
require('./components/svg-icon'),
|
||||
require('./components/warning-modal/warning-modal'),
|
||||
require('./components/safe-webview'),
|
||||
require('./components/file-selector'),
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.FlashErrorModal
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const MODULE_NAME = 'Etcher.Components.FlashErrorModal'
|
||||
const FlashErrorModal = angular.module(MODULE_NAME, [
|
||||
require('../warning-modal/warning-modal')
|
||||
])
|
||||
|
||||
FlashErrorModal.service('FlashErrorModalService', require('./services/flash-error-modal'))
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const flashState = require('../../../models/flash-state')
|
||||
const selectionState = require('../../../models/selection-state')
|
||||
const store = require('../../../models/store')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
|
||||
module.exports = function (WarningModalService) {
|
||||
/**
|
||||
* @summary Open the flash error modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} message - flash error message
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* FlashErrorModalService.show('The drive is not large enough!');
|
||||
*/
|
||||
this.show = (message) => {
|
||||
return WarningModalService.display({
|
||||
confirmationLabel: 'Retry',
|
||||
description: message
|
||||
}).then((confirmed) => {
|
||||
flashState.resetState()
|
||||
|
||||
if (confirmed) {
|
||||
analytics.logEvent('Restart after failure', {
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
} else {
|
||||
selectionState.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.ProgressButton
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.ProgressButton'
|
||||
const ProgressButton = angular.module(MODULE_NAME, [])
|
||||
|
||||
ProgressButton.component(
|
||||
'progressButton',
|
||||
react2angular(require('./progress-button.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -26,7 +26,7 @@ const {
|
||||
keyframes
|
||||
} = require('styled-components')
|
||||
|
||||
const { ProgressBar, Provider } = require('rendition')
|
||||
const { ProgressBar } = require('rendition')
|
||||
|
||||
const { colors } = require('./../../theme')
|
||||
const { StepButton, StepSelection } = require('./../../styled-components')
|
||||
@ -105,46 +105,40 @@ class ProgressButton extends React.Component {
|
||||
if (this.props.active) {
|
||||
if (this.props.striped) {
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<FlashProgressBarValidating
|
||||
primary
|
||||
emphasized
|
||||
value= { this.props.percentage }
|
||||
>
|
||||
{ this.props.label }
|
||||
</FlashProgressBarValidating>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<FlashProgressBar
|
||||
warning
|
||||
<FlashProgressBarValidating
|
||||
primary
|
||||
emphasized
|
||||
value= { this.props.percentage }
|
||||
>
|
||||
{ this.props.label }
|
||||
</FlashProgressBar>
|
||||
</FlashProgressBarValidating>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<StepSelection>
|
||||
<FlashProgressBar
|
||||
warning
|
||||
emphasized
|
||||
value= { this.props.percentage }
|
||||
>
|
||||
{ this.props.label }
|
||||
</FlashProgressBar>
|
||||
</StepSelection>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<StepButton
|
||||
onClick= { this.props.callback }
|
||||
disabled= { this.props.disabled }
|
||||
>
|
||||
{this.props.label}
|
||||
</StepButton>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
<StepSelection>
|
||||
<StepButton
|
||||
onClick= { this.props.callback }
|
||||
disabled= { this.props.disabled }
|
||||
>
|
||||
{this.props.label}
|
||||
</StepButton>
|
||||
</StepSelection>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = function ($uibModalInstance, options) {
|
||||
/**
|
||||
* @summary Modal options
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
this.options = options
|
||||
|
||||
/**
|
||||
* @summary Reject the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* WarningModalController.reject();
|
||||
*/
|
||||
this.reject = () => {
|
||||
$uibModalInstance.close(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Accept the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* WarningModalController.accept();
|
||||
*/
|
||||
this.accept = () => {
|
||||
$uibModalInstance.close(true)
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function ($sce, ModalService) {
|
||||
/**
|
||||
* @summary Display the warning modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} options - options
|
||||
* @param {String} options.description - danger message
|
||||
* @param {String} options.confirmationLabel - confirmation button text
|
||||
* @param {String} options.rejectionLabel - rejection button text
|
||||
* @fulfil {Boolean} - whether the user accepted or rejected the warning
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* WarningModalService.display({
|
||||
* description: 'Don\'t do this!',
|
||||
* confirmationLabel: 'Yes, continue!'
|
||||
* });
|
||||
*/
|
||||
this.display = (options = {}) => {
|
||||
options.description = $sce.trustAsHtml(options.description)
|
||||
return ModalService.open({
|
||||
name: 'warning',
|
||||
template: require('../templates/warning-modal.tpl.html'),
|
||||
controller: 'WarningModalController as modal',
|
||||
size: 'warning-modal',
|
||||
resolve: {
|
||||
options: _.constant(options)
|
||||
}
|
||||
}).result
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.modal-warning-modal .modal-content {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.modal-warning-modal .modal-title .glyphicon {
|
||||
color: $palette-theme-danger-background;
|
||||
}
|
||||
|
||||
.modal-warning-modal .modal-body {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
<span>Attention</span>
|
||||
</h4>
|
||||
<button class="close"
|
||||
tabindex="11"
|
||||
ng-click="modal.reject()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="modal.options.description"></p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="modal-menu">
|
||||
<button class="button button-danger button-block"
|
||||
tabindex="13"
|
||||
ng-click="modal.accept()">{{ ::modal.options.confirmationLabel }}</button>
|
||||
<button ng-if="modal.options.rejectionLabel" class="button button-block"
|
||||
tabindex="12"
|
||||
ng-click="modal.reject()">{{ ::modal.options.rejectionLabel }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.WarningModal
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const MODULE_NAME = 'Etcher.Components.WarningModal'
|
||||
const WarningModal = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal')
|
||||
])
|
||||
|
||||
WarningModal.controller('WarningModalController', require('./controllers/warning-modal'))
|
||||
WarningModal.service('WarningModalService', require('./services/warning-modal'))
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -18,6 +18,9 @@
|
||||
|
||||
const React = require('react')
|
||||
const _ = require('lodash')
|
||||
|
||||
const { Modal, Txt } = require('rendition')
|
||||
const { ThemedProvider } = require('../../styled-components')
|
||||
const messages = require('../../../../shared/messages')
|
||||
const flashState = require('../../models/flash-state')
|
||||
const driveScanner = require('../../modules/drive-scanner')
|
||||
@ -36,49 +39,7 @@ const ProgressButton = require('../../components/progress-button/progress-button
|
||||
const COMPLETED_PERCENTAGE = 100
|
||||
const SPEED_PRECISION = 2
|
||||
|
||||
/**
|
||||
* @summary Spawn a confirmation warning modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<String>} warningMessages - warning messages
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @returns {Promise} warning modal promise
|
||||
*
|
||||
* @example
|
||||
* confirmationWarningModal([ 'Hello, World!' ])
|
||||
*/
|
||||
const confirmationWarningModal = (warningMessages, WarningModalService) => {
|
||||
return WarningModalService.display({
|
||||
confirmationLabel: 'Continue',
|
||||
rejectionLabel: 'Change',
|
||||
description: [
|
||||
warningMessages.join('\n\n'),
|
||||
'Are you sure you want to continue?'
|
||||
].join(' ')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Display warning tailored to the warning of the current drives-image pair
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<Object>} drives - list of drive objects
|
||||
* @param {Object} image - image object
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @returns {Promise<Boolean>}
|
||||
*
|
||||
* @example
|
||||
* displayTailoredWarning(drives, image).then((ok) => {
|
||||
* if (ok) {
|
||||
* console.log('No warning was shown or continue was pressed')
|
||||
* } else {
|
||||
* console.log('Change was pressed')
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
const displayTailoredWarning = async (drives, image, WarningModalService) => {
|
||||
const getWarningMessages = (drives, image) => {
|
||||
const warningMessages = []
|
||||
for (const drive of drives) {
|
||||
if (constraints.isDriveSizeLarge(drive)) {
|
||||
@ -90,29 +51,28 @@ const displayTailoredWarning = async (drives, image, WarningModalService) => {
|
||||
// TODO(Shou): we should consider adding the same warning dialog for system drives and remove unsafe mode
|
||||
}
|
||||
|
||||
if (!warningMessages.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
return confirmationWarningModal(warningMessages, WarningModalService)
|
||||
return warningMessages
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Flash image to drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} $timeout - angular's timeout object
|
||||
* @param {Object} $state - angular's state object
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @param {Object} DriveSelectorService - drive selector service
|
||||
* @param {Object} FlashErrorModalService - flash error modal service
|
||||
*
|
||||
* @example
|
||||
* flashImageToDrive($timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService)
|
||||
*/
|
||||
const flashImageToDrive = async ($timeout, $state,
|
||||
WarningModalService, DriveSelectorService, FlashErrorModalService) => {
|
||||
const getErrorMessageFromCode = (errorCode) => {
|
||||
// TODO: All these error codes to messages translations
|
||||
// should go away if the writer emitted user friendly
|
||||
// messages on the first place.
|
||||
if (errorCode === 'EVALIDATION') {
|
||||
return messages.error.validation()
|
||||
} else if (errorCode === 'EUNPLUGGED') {
|
||||
return messages.error.driveUnplugged()
|
||||
} else if (errorCode === 'EIO') {
|
||||
return messages.error.inputOutput()
|
||||
} else if (errorCode === 'ENOSPC') {
|
||||
return messages.error.notEnoughSpaceInDrive()
|
||||
} else if (errorCode === 'ECHILDDIED') {
|
||||
return messages.error.childWriterDied()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const flashImageToDrive = async ($timeout, $state) => {
|
||||
const devices = selection.getSelectedDevices()
|
||||
const image = selection.getImage()
|
||||
const drives = _.filter(availableDrives.getDrives(), (drive) => {
|
||||
@ -120,20 +80,8 @@ const flashImageToDrive = async ($timeout, $state,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (drives.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus(drives, image)
|
||||
if (hasDangerStatus) {
|
||||
if (!(await displayTailoredWarning(drives, image, WarningModalService))) {
|
||||
DriveSelectorService.open()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (flashState.isFlashing()) {
|
||||
return
|
||||
if (drives.length === 0 || flashState.isFlashing()) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Trigger Angular digests along with store updates, as the flash state
|
||||
@ -160,7 +108,7 @@ const flashImageToDrive = async ($timeout, $state,
|
||||
} catch (error) {
|
||||
// When flashing is cancelled before starting above there is no error
|
||||
if (!error) {
|
||||
return
|
||||
return ''
|
||||
}
|
||||
|
||||
notification.send('Oops! Looks like the flash failed.', {
|
||||
@ -168,29 +116,21 @@ const flashImageToDrive = async ($timeout, $state,
|
||||
icon: iconPath
|
||||
})
|
||||
|
||||
// TODO: All these error codes to messages translations
|
||||
// should go away if the writer emitted user friendly
|
||||
// messages on the first place.
|
||||
if (error.code === 'EVALIDATION') {
|
||||
FlashErrorModalService.show(messages.error.validation())
|
||||
} else if (error.code === 'EUNPLUGGED') {
|
||||
FlashErrorModalService.show(messages.error.driveUnplugged())
|
||||
} else if (error.code === 'EIO') {
|
||||
FlashErrorModalService.show(messages.error.inputOutput())
|
||||
} else if (error.code === 'ENOSPC') {
|
||||
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive())
|
||||
} else if (error.code === 'ECHILDDIED') {
|
||||
FlashErrorModalService.show(messages.error.childWriterDied())
|
||||
} else {
|
||||
FlashErrorModalService.show(messages.error.genericFlashError())
|
||||
let errorMessage = getErrorMessageFromCode(error.code)
|
||||
if (!errorMessage) {
|
||||
error.image = basename
|
||||
analytics.logException(error)
|
||||
errorMessage = messages.error.genericFlashError()
|
||||
}
|
||||
|
||||
return errorMessage
|
||||
} finally {
|
||||
availableDrives.setDrives([])
|
||||
driveScanner.start()
|
||||
unsubscribe()
|
||||
}
|
||||
|
||||
// Return ''
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,7 +165,7 @@ const formatSeconds = (totalSeconds) => {
|
||||
|
||||
const Flash = ({
|
||||
shouldFlashStepBeDisabled, lastFlashErrorCode, progressMessage,
|
||||
$timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService
|
||||
$timeout, $state, DriveSelectorService
|
||||
}) => {
|
||||
// This is a hack to re-render the component whenever the global state changes. Remove once we get rid of angular and use redux correctly.
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
@ -235,53 +175,132 @@ const Flash = ({
|
||||
const isFlashStepDisabled = shouldFlashStepBeDisabled()
|
||||
const flashErrorCode = lastFlashErrorCode()
|
||||
|
||||
const [ warningMessages, setWarningMessages ] = React.useState([])
|
||||
const [ errorMessage, setErrorMessage ] = React.useState('')
|
||||
|
||||
React.useEffect(() => {
|
||||
return store.observe(() => {
|
||||
setRefresh((ref) => !ref)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return <div className="box text-center">
|
||||
<div className="center-block">
|
||||
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={isFlashStepDisabled}/>
|
||||
</div>
|
||||
const handleWarningResponse = async (shouldContinue) => {
|
||||
setWarningMessages([])
|
||||
|
||||
<div className="space-vertical-large">
|
||||
<ProgressButton
|
||||
tabindex="3"
|
||||
striped={state.type === 'verifying'}
|
||||
active={isFlashing}
|
||||
percentage={state.percentage}
|
||||
label={getProgressButtonLabel()}
|
||||
disabled={Boolean(flashErrorCode) || isFlashStepDisabled}
|
||||
callback={() =>
|
||||
flashImageToDrive($timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService)}>
|
||||
</ProgressButton>
|
||||
if (!shouldContinue) {
|
||||
DriveSelectorService.open()
|
||||
return
|
||||
}
|
||||
|
||||
{
|
||||
isFlashing && <button className="button button-link button-abort-write" onClick={imageWriter.cancel}>
|
||||
<span className="glyphicon glyphicon-remove-sign"></span>
|
||||
</button>
|
||||
}
|
||||
{
|
||||
!_.isNil(state.speed) && state.percentage !== COMPLETED_PERCENTAGE &&
|
||||
setErrorMessage(await flashImageToDrive($timeout, $state))
|
||||
}
|
||||
|
||||
const handleFlashErrorResponse = (shouldRetry) => {
|
||||
setErrorMessage('')
|
||||
flashState.resetState()
|
||||
if (shouldRetry) {
|
||||
analytics.logEvent('Restart after failure', {
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
} else {
|
||||
selection.clear()
|
||||
}
|
||||
}
|
||||
|
||||
const tryFlash = async () => {
|
||||
const devices = selection.getSelectedDevices()
|
||||
const image = selection.getImage()
|
||||
const drives = _.filter(availableDrives.getDrives(), (drive) => {
|
||||
return _.includes(devices, drive.device)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (drives.length === 0 || flashState.isFlashing()) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus(drives, image)
|
||||
if (hasDangerStatus) {
|
||||
setWarningMessages(getWarningMessages(drives, image))
|
||||
return
|
||||
}
|
||||
|
||||
setErrorMessage(await flashImageToDrive($timeout, $state))
|
||||
}
|
||||
|
||||
return <ThemedProvider>
|
||||
<div className="box text-center">
|
||||
<div className="center-block">
|
||||
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={isFlashStepDisabled}/>
|
||||
</div>
|
||||
|
||||
<div className="space-vertical-large">
|
||||
<ProgressButton
|
||||
tabindex="3"
|
||||
striped={state.type === 'verifying'}
|
||||
active={isFlashing}
|
||||
percentage={state.percentage}
|
||||
label={getProgressButtonLabel()}
|
||||
disabled={Boolean(flashErrorCode) || isFlashStepDisabled}
|
||||
callback={tryFlash}>
|
||||
</ProgressButton>
|
||||
|
||||
{
|
||||
isFlashing && <button className="button button-link button-abort-write" onClick={imageWriter.cancel}>
|
||||
<span className="glyphicon glyphicon-remove-sign"></span>
|
||||
</button>
|
||||
}
|
||||
{
|
||||
!_.isNil(state.speed) && state.percentage !== COMPLETED_PERCENTAGE &&
|
||||
<p className="step-footer step-footer-split">
|
||||
{Boolean(state.speed) && <span >{`${state.speed.toFixed(SPEED_PRECISION)} MB/s`}</span>}
|
||||
{!_.isNil(state.eta) && <span>{`ETA: ${formatSeconds(state.eta)}` }</span>}
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Boolean(state.failed) && <div className="target-status-wrap">
|
||||
<div className="target-status-line target-status-failed">
|
||||
<span className="target-status-dot"></span>
|
||||
<span className="target-status-quantity">{state.failed}</span>
|
||||
<span className="target-status-message">{progressMessage.failed(state.failed)} </span>
|
||||
{
|
||||
Boolean(state.failed) && <div className="target-status-wrap">
|
||||
<div className="target-status-line target-status-failed">
|
||||
<span className="target-status-dot"></span>
|
||||
<span className="target-status-quantity">{state.failed}</span>
|
||||
<span className="target-status-message">{progressMessage.failed(state.failed)} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* eslint-disable-next-line no-magic-numbers */}
|
||||
{warningMessages && warningMessages.length > 0 && <Modal
|
||||
width={400}
|
||||
titleElement={'Attention'}
|
||||
cancel={() => handleWarningResponse(false)}
|
||||
done={() => handleWarningResponse(true)}
|
||||
cancelButtonProps={{
|
||||
children: 'Change'
|
||||
}}
|
||||
action={'Continue'}
|
||||
primaryButtonProps={{ primary: false, warning: true }}
|
||||
>
|
||||
{
|
||||
_.map(warningMessages, (message) => <Txt whitespace="pre-line" mt={2}>{message}</Txt>)
|
||||
}
|
||||
</Modal>
|
||||
}
|
||||
|
||||
{errorMessage && <Modal
|
||||
width={400}
|
||||
titleElement={'Attention'}
|
||||
cancel={() => handleFlashErrorResponse(false)}
|
||||
done={() => handleFlashErrorResponse(true)}
|
||||
action={'Retry'}
|
||||
>
|
||||
<Txt>{errorMessage}</Txt>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
</ThemedProvider>
|
||||
}
|
||||
|
||||
module.exports = Flash
|
||||
|
@ -31,10 +31,7 @@ const MainPage = angular.module(MODULE_NAME, [
|
||||
|
||||
require('../../components/drive-selector/drive-selector'),
|
||||
require('../../components/tooltip-modal/tooltip-modal'),
|
||||
require('../../components/flash-error-modal/flash-error-modal'),
|
||||
require('../../components/progress-button'),
|
||||
require('../../components/image-selector'),
|
||||
require('../../components/warning-modal/warning-modal'),
|
||||
require('../../components/file-selector'),
|
||||
require('../../components/featured-project'),
|
||||
require('../../components/reduced-flashing-infos'),
|
||||
@ -61,7 +58,7 @@ MainPage.component('driveSelector', react2angular(require('./DriveSelector.jsx')
|
||||
))
|
||||
MainPage.component('flash', react2angular(require('./Flash.jsx'),
|
||||
[ 'shouldFlashStepBeDisabled', 'lastFlashErrorCode', 'progressMessage' ],
|
||||
[ '$timeout', '$state', 'WarningModalService', 'DriveSelectorService', 'FlashErrorModalService' ]))
|
||||
[ '$timeout', '$state', 'DriveSelectorService' ]))
|
||||
|
||||
MainPage.config(($stateProvider) => {
|
||||
$stateProvider
|
||||
|
@ -35,7 +35,6 @@ $disabled-opacity: 0.2;
|
||||
@import "../components/drive-selector/styles/drive-selector";
|
||||
@import "../components/svg-icon/styles/svg-icon";
|
||||
@import "../components/tooltip-modal/styles/tooltip-modal";
|
||||
@import "../components/warning-modal/styles/warning-modal";
|
||||
@import "../components/file-selector/styles/file-selector";
|
||||
@import "../pages/main/styles/main";
|
||||
@import "../pages/finish/styles/finish";
|
||||
|
27
npm-shrinkwrap.json
generated
27
npm-shrinkwrap.json
generated
@ -9171,11 +9171,6 @@
|
||||
"sinon-chai": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
},
|
||||
"moment-mini": {
|
||||
"version": "2.22.1",
|
||||
"resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.22.1.tgz",
|
||||
@ -10781,28 +10776,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dropzone": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.1.tgz",
|
||||
"integrity": "sha512-Me5nOu8hK9/Xyg5easpdfJ6SajwUquqYR/2YTdMotsCUgJ1pHIIwNsv0n+qcIno0tWR2V2rVQtj2r/hXYs2TnQ==",
|
||||
"requires": {
|
||||
"attr-accept": "^2.0.0",
|
||||
"file-selector": "^0.1.12",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-google-recaptcha": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz",
|
||||
|
Loading…
x
Reference in New Issue
Block a user