Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetpack Cloud: Create Fix all threats - add credentials modal #40209

Merged
merged 13 commits into from
Mar 26, 2020
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* eslint-disable no-console */
/**
* External dependencies
*/
import React from 'react';
import { translate } from 'i18n-calypso';
import { Button, Dialog } from '@automattic/components';

/**
* Internal dependencies
*/
import Gridicon from 'components/gridicon';
import ServerCredentialsForm from 'landing/jetpack-cloud/components/server-credentials-form';
import { Threat } from 'landing/jetpack-cloud/components/threat-item/types';

/**
* Style dependencies
*/
import './style.scss';

interface Props {
onCloseDialog: Function;
onConfirmation: Function;
showDialog: boolean;
siteId: number;
threats: Array< Threat >;
userHasCredentials: boolean;
}

type ProcessStep = 'server-credentials' | 'confirmation';

const FixAllThreatsDialog = ( {
onConfirmation,
onCloseDialog,
showDialog,
siteId,
threats,
userHasCredentials = true,
}: Props ) => {
const firstStep = userHasCredentials ? 'confirmation' : 'server-credentials';
const [ currentStep, setCurrentStep ] = React.useState< ProcessStep >( firstStep );

return (
<Dialog additionalClassNames="fix-all-threats-dialog" isVisible={ showDialog }>
<h1 className="fix-all-threats-dialog__header">Fix all threats</h1>
{ currentStep === 'server-credentials' && (
<>
<h3 className="fix-all-threats-dialog__threat-title">
{ translate( 'You have selected to fix all discovered threats' ) }
</h3>
<div className="fix-all-threats-dialog__warning">
<Gridicon
className="fix-all-threats-dialog__icon fix-all-threats-dialog__icon--warning"
icon="info"
size={ 36 }
/>
<p className="fix-all-threats-dialog__warning-message">
{ translate(
"Jetpack is unable to auto fix these threats as we currently do not have access to your website's server. Please supply your SFTP/SSH credentials to enable auto fixing. Alternatively, you will need go back and {{strong}}fix the threats manually{{/strong}}.",
{
components: {
strong: <strong />,
},
}
) }
</p>
</div>

<ServerCredentialsForm
className="fix-all-threats-dialog__form"
onCancel={ onCloseDialog }
onComplete={ () => setCurrentStep( 'confirmation' ) }
role="main"
siteId={ siteId }
labels={ {
cancel: translate( 'Go back' ),
save: translate( 'Save credentials and fix' ),
} }
/>
</>
) }
{ currentStep === 'confirmation' && (
<>
<h3 className="fix-all-threats-dialog__threat-title">
{ translate( 'Please confirm you want to fix all %(threatCount)d active threats', {
args: {
threatCount: threats.length,
},
} ) }
</h3>
<p className="fix-all-threats-dialog__warning-message">
{ translate( 'Jetpack will be fixing all the detected active threats.' ) }
</p>
<div className="fix-all-threats-dialog__warning">
<Gridicon
className="fix-all-threats-dialog__icon fix-all-threats-dialog__icon--confirmation"
icon="info"
size={ 36 }
/>
<div className="fix-all-threats-dialog__warning-message">
{ translate( 'To fix this threat, Jetpack will be:' ) }
<ul>
{ threats.map( threat => (
<li key={ threat.id }>{ threat.title }</li>
) ) }
</ul>
</div>
</div>
<div className="fix-all-threats-dialog__buttons">
{ firstStep !== currentStep && (
<Button
className="fix-all-threats-dialog__btn fix-all-threats-dialog__btn--cancel"
onClick={ () => setCurrentStep( 'server-credentials' ) }
>
Go back
</Button>
) }
<Button
className="fix-all-threats-dialog__btn fix-all-threats-dialog__btn--fix"
onClick={ onConfirmation }
>
Fix all threats
</Button>
</div>
</>
) }
</Dialog>
);
};

export default FixAllThreatsDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.dialog.fix-all-threats-dialog {
max-width: 676px;

.fix-all-threats-dialog {
&__header {
font-size: 18px;
border-bottom: 1px solid #dcdcde;
margin: -8px -24px 16px;
padding: 0 24px 16px;
color: var( --color-primary-40 );
}

&__threat-title {
font-weight: bold;
margin: 8px 0;
}

&__warning {
display: flex;
}

&__icon {
flex: 0 0 24px;
margin-top: -5px;
margin-right: 8px;
}

&__icon--warning {
fill: var( --color-threat-found );
}

&__icon--confirmation {
fill: var( --color-primary-40 );
}

&__warning-message {
color: var( --color-text-subtle );
font-size: 14px;
}

&__buttons {
display: flex;
justify-content: center;
}

&__btn {
width: 100%;

@include breakpoint( '>480px' ) {
width: 200px;
}
}

&__btn--fix {
background: var( --color-primary-40 );
color: #fff;
border: 1px solid var( --studio-gray-0 );
}

&__btn--cancel {
background: #fff;
color: var( --color-primary-40 );
border: 1px solid var( --color-primary-40 );
margin-right: 16px;
}
}
}
72 changes: 62 additions & 10 deletions client/landing/jetpack-cloud/components/scan-threats/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
* External dependencies
*/
import React from 'react';
import { connect } from 'react-redux';
import { numberFormat, translate } from 'i18n-calypso';
import { isEmpty } from 'lodash';
import { Button } from '@automattic/components';

/**
* Internal dependencies
*/
import FixAllThreatsDialog from '../../components/fix-all-threats-dialog';
import SecurityIcon from 'landing/jetpack-cloud/components/security-icon';
import ThreatDialog from 'landing/jetpack-cloud/components/threat-dialog';
import ThreatItem from 'landing/jetpack-cloud/components/threat-item';
import { Threat, ThreatAction } from 'landing/jetpack-cloud/components/threat-item/types';
import getJetpackCredentials from 'state/selectors/get-jetpack-credentials';

/**
* Style dependencies
Expand All @@ -18,36 +24,51 @@ import './style.scss';

interface Props {
site: {
ID: number;
name: string;
};
threats: Array< Threat >;
userHasCredentials: boolean;
}

const ScanThreats = ( { site, threats }: Props ) => {
const [ selectedThreat, setSelectedThreat ] = React.useState< Threat | undefined >();
const ScanThreats = ( { site, threats, userHasCredentials }: Props ) => {
const [ fixingThreats, setFixingThreats ] = React.useState< Array< Threat > >( [] );
const [ selectedThreat, setSelectedThreat ] = React.useState< Threat >( threats[ 0 ] );
const [ showThreatDialog, setShowThreatDialog ] = React.useState( false );
const [ showFixAllThreatsDialog, setShowFixAllThreatsDialog ] = React.useState( false );
const [ actionToPerform, setActionToPerform ] = React.useState< ThreatAction >( 'fix' );

const openDialog = ( action: ThreatAction, threat: Threat ) => {
const openFixAllThreatsDialog = React.useCallback( () => {
setShowFixAllThreatsDialog( true );
}, [] );

const openDialog = React.useCallback( ( action: ThreatAction, threat: Threat ) => {
setSelectedThreat( threat );
setActionToPerform( action );
setShowThreatDialog( true );
};
}, [] );

const closeDialog = () => {
setSelectedThreat( undefined );
const closeDialog = React.useCallback( () => {
setShowThreatDialog( false );
};
}, [] );

const confirmAction = () => {
const confirmAction = React.useCallback( () => {
window.alert(
`We are going to ${ actionToPerform } threat ${ selectedThreat?.id } on site ${ site.name }`
);
closeDialog();
};
setFixingThreats( stateThreats => [ ...stateThreats, selectedThreat ] );
}, [ actionToPerform, closeDialog, selectedThreat, site ] );

const confirmFixAllThreats = React.useCallback( () => {
window.alert( `Starting to fix ${ threats.length } threats found...` );
setShowFixAllThreatsDialog( false );
setFixingThreats( threats );
}, [ threats ] );

return (
<>
<SecurityIcon icon="error" />
<h1 className="scan-threats scan__header">{ translate( 'Your site may be at risk' ) }</h1>
<p>
{ translate(
Expand All @@ -74,12 +95,29 @@ const ScanThreats = ( { site, threats }: Props ) => {
) }
</p>
<div className="scan-threats__threats">
<div className="scan-threats__buttons">
<Button
className="scan-threats__fix-all-threats-button"
onClick={ openFixAllThreatsDialog }
disabled={ fixingThreats.length === threats.length }
>
{ translate( 'Fix all' ) }
</Button>
<Button
className="scan-threats__options-button"
onClick={ openFixAllThreatsDialog }
disabled={ fixingThreats.length === threats.length }
>
...
</Button>
</div>
{ threats.map( threat => (
<ThreatItem
key={ threat.id }
threat={ threat }
onFixThreat={ () => openDialog( 'fix', threat ) }
onIgnoreThreat={ () => openDialog( 'ignore', threat ) }
isFixing={ !! fixingThreats.find( t => t.id === threat.id ) }
/>
) ) }
</div>
Expand All @@ -94,8 +132,22 @@ const ScanThreats = ( { site, threats }: Props ) => {
action={ actionToPerform }
/>
) }
<FixAllThreatsDialog
threats={ threats }
showDialog={ showFixAllThreatsDialog }
siteId={ site.ID }
onCloseDialog={ () => setShowFixAllThreatsDialog( false ) }
onConfirmation={ confirmFixAllThreats }
userHasCredentials={ userHasCredentials }
/>
</>
);
};

export default ScanThreats;
const mapStateToProps = ( state, { site } ) => {
return {
userHasCredentials: ! isEmpty( getJetpackCredentials( state, site.ID, 'main' ) ),
};
};

export default connect( mapStateToProps )( ScanThreats );
31 changes: 31 additions & 0 deletions client/landing/jetpack-cloud/components/scan-threats/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,35 @@
&__threats {
margin: 40px 0;
}

&__buttons {
display: flex;
justify-content: space-between;
margin-bottom: 16px;

@include breakpoint( '>800px' ) {
justify-content: flex-end;
}
}

&__fix-all-threats-button {
flex: 1 1 75%;
padding: 10px 30px;
background: var( --color-primary-40 );
color: #fff;
border-color: var( --color-primary-40 );

@include breakpoint( '>800px' ) {
flex: 0 0 170px;
}
}

&__options-button {
flex: 0 0 50px;
padding: 10px 0;
margin-left: 16px;
color: var( --color-primary-40 );
background: #fff;
border-color: var( --color-primary-40 );
}
}
Loading