From b91975605e5de9d6b7ccf886ccff3795bbd21fc6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:03:25 -0800 Subject: [PATCH 001/290] Add ThreatDetailsModal component and stories --- .../components/threat-details-modal/index.tsx | 274 ++++++++++++++++++ .../stories/index.stories.tsx | 67 +++++ .../threat-details-modal/styles.module.scss | 35 +++ .../components/threat-fixer-button/index.tsx | 65 +---- projects/js-packages/scan/src/utils/index.ts | 74 +++++ 5 files changed, 455 insertions(+), 60 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/index.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx new file mode 100644 index 0000000000000..cd718a5ed51c5 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -0,0 +1,274 @@ +import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { + type Threat, + fixerIsInError, + fixerIsInProgress, + fixerStatusIsStale, + getFixerAction, + getFixerMessage, +} from '@automattic/jetpack-scan'; +import { Modal, Notice } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo, useCallback } from 'react'; +import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; +import DiffViewer from '../diff-viewer'; +import MarkedLines from '../marked-lines'; +import Text from '../text'; +import styles from './styles.module.scss'; + +const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ) => { + if ( ! threat.filename && ! threat.context && ! threat.diff ) { + return null; + } + + return ( +
+ { __( 'The technical details', 'jetpack' ) } + { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ); +}; + +const ThreatFixDetails = ( { + threat, + handleUpgradeClick, +}: { + threat: Threat; + handleUpgradeClick: () => void; +} ) => { + const title = useMemo( () => { + if ( threat.status === 'fixed' ) { + return __( 'How did Jetpack fix it?', 'jetpack' ); + } + if ( threat.status === 'current' && threat.fixable ) { + return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); + } + return __( 'How to fix it?', 'jetpack' ); + }, [ threat ] ); + + const fix = useMemo( () => { + // The threat has a fixed version available, but no auto-fix is available. + // The user needs to update the extension to the fixed version. + if ( ! threat.fixable && threat.fixedIn ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Update %1$s to version %2$s.', 'jetpack' ), + threat.extension.name, + threat.fixedIn + ); + } + + // The threat has an auto-fix available. + return getFixerMessage( threat ); + }, [ threat ] ); + + if ( ! threat.fixable && ! threat.fixedIn ) { + return null; + } + + return ( +
+ { title } + { fix } + + { !! handleUpgradeClick && ( + + ) } +
+ ); +}; + +const ThreatActions = ( { + threat, + closeModal, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + fixerState, +}: { + threat: Threat; + closeModal: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + const fixerAction = useMemo( () => { + return getFixerAction( threat ); + }, [ threat ] ); + + const onFixClick = useCallback( () => { + handleFixThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleFixThreatClick, closeModal ] ); + + const onIgnoreClick = useCallback( () => { + handleIgnoreThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleIgnoreThreatClick, closeModal ] ); + + const onUnignoreClick = useCallback( () => { + handleUnignoreThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleUnignoreThreatClick, closeModal ] ); + + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { + return null; + } + + return ( +
+
+ +
+
+ { 'ignored' === threat.status && !! handleUnignoreThreatClick && ( + + ) } + { 'current' === threat.status && ( + <> + { !! handleIgnoreThreatClick && ( + + ) } + { threat.fixable && !! handleFixThreatClick && ( + + ) } + + ) } +
+
+ ); +}; + +/** + * ThreatDetailsModal component + * + * @param {object} props - The props. + * @param {object} props.threat - The threat. + * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. + * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. + * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. + * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. + * + * @return {JSX.Element} The threat details modal. + */ +export default function ThreatDetailsModal( { + threat, + handleUpgradeClick, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + ...modalProps +}: { + threat: Threat; + handleUpgradeClick?: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + [ key: string ]: unknown; +} ): JSX.Element { + const fixerState = useMemo( () => { + const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); + const error = threat.fixer && fixerIsInError( threat.fixer ); + const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); + return { inProgress, error, stale }; + }, [ threat.fixer ] ); + + const title = useMemo( () => { + if ( threat.title ) { + return threat.title; + } + + if ( threat.status === 'fixed' ) { + return __( 'What was the problem?', 'jetpack' ); + } + + return __( 'What is the problem?', 'jetpack' ); + }, [ threat ] ); + + return ( + +
+ { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
+ + { !! threat.description && { threat.description } } + + { !! threat.source && ( +
+ +
+ ) } +
+ + + + + + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> +
+
+ ); +} diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx new file mode 100644 index 0000000000000..46b1e6a93baea --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -0,0 +1,67 @@ +import { useCallback, useState } from 'react'; +import Button from '../../button/index.js'; +import ThreatDetailsModal from '../index.js'; + +export default { + title: 'JS Packages/Components/Threat Details Modal', + component: ThreatDetailsModal, +}; + +const Base = args => { + const [ isOpen, setIsOpen ] = useState( false ); + const onClick = useCallback( () => setIsOpen( true ), [] ); + const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + return ( +
+ + { isOpen ? : null } +
+ ); +}; + +export const ThreatResult = Base.bind( {} ); +ThreatResult.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const VulnerableExtension = Base.bind( {} ); +VulnerableExtension.args = { + threat: { + id: 184847701, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', + description: + 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', + fixedIn: '1.12.4', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'WP Super Cache', + slug: 'wp-super-cache', + version: '1.6.3', + type: 'plugin', + }, + }, + handleUpgradeClick: () => {}, +}; diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-details-modal/styles.module.scss new file mode 100644 index 0000000000000..528c9e5f25ba5 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/styles.module.scss @@ -0,0 +1,35 @@ +.threat-details { + display: flex; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px +} + +.section { + display: flex; + flex-direction: column; + gap: calc( var( --spacing-base ) * 2 ); // 16px +} + +.title { + display: flex; + align-items: center; + gap: calc( var( --spacing-base ) * 1.5 ); // 12px +} + +.filename { + background-color: var( --jp-gray-0 ); + padding: calc( var( --spacing-base ) * 3 ); // 24px + overflow-x: auto; +} + +.modal-actions { + display: flex; + justify-content: space-between; + padding-top: calc( var( --spacing-base ) * 3 ); // 24px + border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index cd15c0294c313..a2ef72666c23a 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -4,6 +4,8 @@ import { fixerIsInError, fixerIsInProgress, fixerStatusIsStale, + getFixerAction, + getFixerMessage, } from '@automattic/jetpack-scan'; import { Tooltip } from '@wordpress/components'; import { useCallback, useMemo } from '@wordpress/element'; @@ -53,54 +55,7 @@ export default function ThreatFixerButton( { return __( 'An auto-fixer is in progress.', 'jetpack' ); } - switch ( threat.fixable.fixer ) { - case 'delete': - if ( threat.filename ) { - if ( threat.filename.endsWith( '/' ) ) { - return __( 'Deletes the directory that the infected file is in.', 'jetpack' ); - } - - if ( threat.signature === 'Core.File.Modification' ) { - return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack' ); - } - - return __( 'Deletes the infected file.', 'jetpack' ); - } - - if ( threat.extension?.type === 'plugin' ) { - return __( 'Deletes the plugin directory to fix the threat.', 'jetpack' ); - } - - if ( threat.extension?.type === 'theme' ) { - return __( 'Deletes the theme directory to fix the threat.', 'jetpack' ); - } - break; - case 'update': - return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack' ); - case 'replace': - case 'rollback': - if ( threat.filename ) { - return threat.signature === 'Core.File.Modification' - ? __( - 'Replaces the modified core WordPress file with the original clean version from the WordPress source code.', - 'jetpack' - ) - : __( - 'Replaces the infected file with a previously backed up version that is clean.', - 'jetpack' - ); - } - - if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { - return __( - 'Replaces the default salt keys in wp-config.php with unique ones.', - 'jetpack' - ); - } - break; - default: - return __( 'An auto-fixer is available.', 'jetpack' ); - } + return getFixerMessage( threat ); }, [ threat, fixerState ] ); const buttonText = useMemo( () => { @@ -112,18 +67,8 @@ export default function ThreatFixerButton( { return __( 'Error', 'jetpack' ); } - switch ( threat.fixable.fixer ) { - case 'delete': - return __( 'Delete', 'jetpack' ); - case 'update': - return __( 'Update', 'jetpack' ); - case 'replace': - case 'rollback': - return __( 'Replace', 'jetpack' ); - default: - return __( 'Fix', 'jetpack' ); - } - }, [ threat.fixable, fixerState.error ] ); + return getFixerAction( threat ); + }, [ threat, fixerState.error ] ); const handleClick = useCallback( ( event: React.MouseEvent ) => { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index be4fe59047694..6023e76f7e0d6 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -1,3 +1,4 @@ +import { __, sprintf } from '@wordpress/i18n'; import { Threat, ThreatFixStatus, FIXER_IS_STALE_THRESHOLD } from '..'; export const getThreatType = ( threat: Threat ) => { @@ -35,3 +36,76 @@ export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => { fixerTimestampIsStale( fixerStatus.lastUpdated ) ); }; + +export const getFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + return __( 'Delete', 'jetpack-scan' ); + case 'update': + return __( 'Update', 'jetpack-scan' ); + case 'replace': + case 'rollback': + return __( 'Replace', 'jetpack-scan' ); + default: + return __( 'Fix', 'jetpack-scan' ); + } +}; + +export const getFixerMessage = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + if ( threat.filename.endsWith( '/' ) ) { + return __( 'Deletes the directory that the infected file is in.', 'jetpack-scan' ); + } + + if ( threat.signature === 'Core.File.Modification' ) { + return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack-scan' ); + } + + return __( 'Deletes the infected file.', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Deletes the plugin directory to fix the threat.', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Deletes the theme directory to fix the threat.', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.fixedIn && threat.extension.name ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Updates %1$s to version %2$s', 'jetpack-scan' ), + threat.extension.name, + threat.fixedIn + ); + } + return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack-scan' ); + case 'replace': + case 'rollback': + if ( threat.filename ) { + return threat.signature === 'Core.File.Modification' + ? __( + 'Replaces the modified core WordPress file with the original clean version from the WordPress source code.', + 'jetpack-scan' + ) + : __( + 'Replaces the infected file with a previously backed up version that is clean.', + 'jetpack-scan' + ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( + 'Replaces the default salt keys in wp-config.php with unique ones.', + 'jetpack-scan' + ); + } + break; + default: + return __( 'Jetpack will auto-fix the threat.', 'jetpack-scan' ); + } +}; From 15587ca13a8d83dee346d24cd57975840f2fed4e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:04:15 -0800 Subject: [PATCH 002/290] changelog --- .../components/changelog/add-components-threat-details-modal | 4 ++++ .../scan/changelog/add-components-threat-details-modal | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 projects/js-packages/components/changelog/add-components-threat-details-modal create mode 100644 projects/js-packages/scan/changelog/add-components-threat-details-modal diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal new file mode 100644 index 0000000000000..3caa1f5d56878 --- /dev/null +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ThreatDetailsModal component and stories diff --git a/projects/js-packages/scan/changelog/add-components-threat-details-modal b/projects/js-packages/scan/changelog/add-components-threat-details-modal new file mode 100644 index 0000000000000..3caa1f5d56878 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-components-threat-details-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ThreatDetailsModal component and stories From 2bebb0df357d74e732006958e4ffe70859057f0c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:13:09 -0800 Subject: [PATCH 003/290] Fix type error --- projects/js-packages/scan/src/utils/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 6023e76f7e0d6..2f473ac0a22ad 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,19 +21,19 @@ export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { return now.getTime() - lastUpdated.getTime() >= FIXER_IS_STALE_THRESHOLD; }; -export const fixerIsInError = ( fixerStatus: ThreatFixStatus ) => { - return 'error' in fixerStatus && fixerStatus.error; +export const fixerIsInError = ( fixerStatus: ThreatFixStatus ): boolean => { + return !! ( 'error' in fixerStatus && fixerStatus.error ); }; -export const fixerIsInProgress = ( fixerStatus: ThreatFixStatus ) => { - return 'status' in fixerStatus && fixerStatus.status === 'in_progress'; +export const fixerIsInProgress = ( fixerStatus: ThreatFixStatus ): boolean => { + return !! ( 'status' in fixerStatus && fixerStatus.status === 'in_progress' ); }; -export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => { +export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ): boolean => { return ( fixerIsInProgress( fixerStatus ) && 'lastUpdated' in fixerStatus && - fixerTimestampIsStale( fixerStatus.lastUpdated ) + !! fixerTimestampIsStale( fixerStatus.lastUpdated ) ); }; From dddfdaf58662e6f9c05c6220f7e87a666f5ecda6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:16:33 -0800 Subject: [PATCH 004/290] Fix child component overflow-x styling --- .../components/components/diff-viewer/styles.module.scss | 2 +- .../components/components/marked-lines/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/js-packages/components/components/diff-viewer/styles.module.scss b/projects/js-packages/components/components/diff-viewer/styles.module.scss index 16c44d670f7f9..3e7f2eab65375 100644 --- a/projects/js-packages/components/components/diff-viewer/styles.module.scss +++ b/projects/js-packages/components/components/diff-viewer/styles.module.scss @@ -14,7 +14,7 @@ display: flex; font-family: "Courier 10 Pitch", Courier, monospace; flex-direction: row; - overflow-x: scroll; + overflow-x: auto; white-space: pre; } diff --git a/projects/js-packages/components/components/marked-lines/styles.module.scss b/projects/js-packages/components/components/marked-lines/styles.module.scss index 49733a4d9ebd2..dab9e17fcafb2 100644 --- a/projects/js-packages/components/components/marked-lines/styles.module.scss +++ b/projects/js-packages/components/components/marked-lines/styles.module.scss @@ -4,7 +4,7 @@ font-family: monospace; display: flex; flex-direction: row; - overflow-x: scroll; + overflow-x: auto; } .marked-lines__marked-line { From 172fbb0360d0786b933dee00990d486629005845 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 14:37:00 -0800 Subject: [PATCH 005/290] Update scan package changelog --- .../scan/changelog/add-components-threat-details-modal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/scan/changelog/add-components-threat-details-modal b/projects/js-packages/scan/changelog/add-components-threat-details-modal index 3caa1f5d56878..a81430663b3df 100644 --- a/projects/js-packages/scan/changelog/add-components-threat-details-modal +++ b/projects/js-packages/scan/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds utilities for retrieving fixer messaging From d39a616b8b41c84d747f5f3423ab5e28b8f06a5d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 15:24:26 -0800 Subject: [PATCH 006/290] Add util for fixerState and separate subcomponents --- .../components/threat-details-modal/index.tsx | 178 +----------------- .../threat-details-modal/threat-actions.tsx | 102 ++++++++++ .../threat-fix-details.tsx | 68 +++++++ .../threat-technical-details.tsx | 36 ++++ .../components/threat-fixer-button/index.tsx | 9 +- projects/js-packages/scan/src/utils/index.ts | 8 + 6 files changed, 223 insertions(+), 178 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-actions.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index cd718a5ed51c5..79e14047fa3ca 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,174 +1,13 @@ import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components'; -import { - type Threat, - fixerIsInError, - fixerIsInProgress, - fixerStatusIsStale, - getFixerAction, - getFixerMessage, -} from '@automattic/jetpack-scan'; +import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal, Notice } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo, useCallback } from 'react'; -import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; -import DiffViewer from '../diff-viewer'; -import MarkedLines from '../marked-lines'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; import Text from '../text'; import styles from './styles.module.scss'; - -const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ) => { - if ( ! threat.filename && ! threat.context && ! threat.diff ) { - return null; - } - - return ( -
- { __( 'The technical details', 'jetpack' ) } - { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } -
- ); -}; - -const ThreatFixDetails = ( { - threat, - handleUpgradeClick, -}: { - threat: Threat; - handleUpgradeClick: () => void; -} ) => { - const title = useMemo( () => { - if ( threat.status === 'fixed' ) { - return __( 'How did Jetpack fix it?', 'jetpack' ); - } - if ( threat.status === 'current' && threat.fixable ) { - return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); - } - return __( 'How to fix it?', 'jetpack' ); - }, [ threat ] ); - - const fix = useMemo( () => { - // The threat has a fixed version available, but no auto-fix is available. - // The user needs to update the extension to the fixed version. - if ( ! threat.fixable && threat.fixedIn ) { - return sprintf( - /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ - __( 'Update %1$s to version %2$s.', 'jetpack' ), - threat.extension.name, - threat.fixedIn - ); - } - - // The threat has an auto-fix available. - return getFixerMessage( threat ); - }, [ threat ] ); - - if ( ! threat.fixable && ! threat.fixedIn ) { - return null; - } - - return ( -
- { title } - { fix } - - { !! handleUpgradeClick && ( - - ) } -
- ); -}; - -const ThreatActions = ( { - threat, - closeModal, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, - fixerState, -}: { - threat: Threat; - closeModal: () => void; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ) => { - const fixerAction = useMemo( () => { - return getFixerAction( threat ); - }, [ threat ] ); - - const onFixClick = useCallback( () => { - handleFixThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleFixThreatClick, closeModal ] ); - - const onIgnoreClick = useCallback( () => { - handleIgnoreThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleIgnoreThreatClick, closeModal ] ); - - const onUnignoreClick = useCallback( () => { - handleUnignoreThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleUnignoreThreatClick, closeModal ] ); - - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - - return ( -
-
- -
-
- { 'ignored' === threat.status && !! handleUnignoreThreatClick && ( - - ) } - { 'current' === threat.status && ( - <> - { !! handleIgnoreThreatClick && ( - - ) } - { threat.fixable && !! handleFixThreatClick && ( - - ) } - - ) } -
-
- ); -}; +import ThreatActions from './threat-actions'; +import ThreatFixDetails from './threat-fix-details'; +import ThreatTechnicalDetails from './threat-technical-details'; /** * ThreatDetailsModal component @@ -198,10 +37,7 @@ export default function ThreatDetailsModal( { [ key: string ]: unknown; } ): JSX.Element { const fixerState = useMemo( () => { - const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); - const error = threat.fixer && fixerIsInError( threat.fixer ); - const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); - return { inProgress, error, stale }; + return getFixerState( threat.fixer ); }, [ threat.fixer ] ); const title = useMemo( () => { diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx new file mode 100644 index 0000000000000..d14a2901fb4bf --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -0,0 +1,102 @@ +import { Button } from '@automattic/jetpack-components'; +import { Threat, getFixerAction } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import React, { useCallback, useMemo } from 'react'; +import styles from './styles.module.scss'; + +/** + * ThreatActions component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing action details. + * @param {Function} props.closeModal - Function to close the modal. + * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. + * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. + * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer is stale. + * + * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. + */ +const ThreatActions = ( { + threat, + closeModal, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + fixerState, +}: { + threat: Threat; + closeModal: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ): JSX.Element => { + const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); + + const onFixClick = useCallback( () => { + handleFixThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleFixThreatClick, closeModal ] ); + + const onIgnoreClick = useCallback( () => { + handleIgnoreThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleIgnoreThreatClick, closeModal ] ); + + const onUnignoreClick = useCallback( () => { + handleUnignoreThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleUnignoreThreatClick, closeModal ] ); + + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { + return null; + } + + return ( +
+
+ +
+
+ { threat.status === 'ignored' && handleUnignoreThreatClick && ( + + ) } + { threat.status === 'current' && ( + <> + { handleIgnoreThreatClick && ( + + ) } + { threat.fixable && handleFixThreatClick && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx new file mode 100644 index 0000000000000..441348aadc92b --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx @@ -0,0 +1,68 @@ +import { Threat, getFixerMessage } from '@automattic/jetpack-scan'; +import { __, sprintf } from '@wordpress/i18n'; +import React, { useMemo } from 'react'; +import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; +import Text from '../text'; +import styles from './styles.module.scss'; + +/** + * ThreatFixDetails component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing fix details. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade click events. + * + * @return {JSX.Element | null} The rendered fix details or null if no fixable details are available. + */ +const ThreatFixDetails = ( { + threat, + handleUpgradeClick, +}: { + threat: Threat; + handleUpgradeClick: () => void; +} ): JSX.Element => { + const title = useMemo( () => { + if ( threat.status === 'fixed' ) { + return __( 'How did Jetpack fix it?', 'jetpack' ); + } + if ( threat.status === 'current' && threat.fixable ) { + return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); + } + return __( 'How to fix it?', 'jetpack' ); + }, [ threat ] ); + + const fix = useMemo( () => { + // The threat has a fixed version available, but no auto-fix is available. + // The user needs to update the extension to the fixed version. + if ( ! threat.fixable && threat.fixedIn ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Update %1$s to version %2$s.', 'jetpack' ), + threat.extension.name, + threat.fixedIn + ); + } + // The threat has an auto-fix available. + return getFixerMessage( threat ); + }, [ threat ] ); + + if ( ! threat.fixable && ! threat.fixedIn ) { + return null; + } + + return ( +
+ { title } + { fix } + { handleUpgradeClick && ( + + ) } +
+ ); +}; + +export default ThreatFixDetails; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx new file mode 100644 index 0000000000000..38793f704886d --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx @@ -0,0 +1,36 @@ +import { Threat } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import DiffViewer from '../diff-viewer'; +import MarkedLines from '../marked-lines'; +import Text from '../text'; +import styles from './styles.module.scss'; + +/** + * ThreatTechnicalDetails component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing technical details. + * + * @return {JSX.Element | null} The rendered technical details or null if no details are available. + */ +const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { + if ( ! threat.filename && ! threat.context && ! threat.diff ) { + return null; + } + + return ( +
+ { __( 'The technical details', 'jetpack' ) } + { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ); +}; + +export default ThreatTechnicalDetails; diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index a2ef72666c23a..11520c139ceee 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -1,9 +1,7 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, - fixerIsInError, - fixerIsInProgress, - fixerStatusIsStale, + getFixerState, getFixerAction, getFixerMessage, } from '@automattic/jetpack-scan'; @@ -32,10 +30,7 @@ export default function ThreatFixerButton( { className?: string; } ): JSX.Element { const fixerState = useMemo( () => { - const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); - const error = threat.fixer && fixerIsInError( threat.fixer ); - const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); - return { inProgress, error, stale }; + return getFixerState( threat.fixer ); }, [ threat.fixer ] ); const tooltipText = useMemo( () => { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 2f473ac0a22ad..8b186b065bb33 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -37,6 +37,14 @@ export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ): boolean => { ); }; +export const getFixerState = ( fixerStatus: ThreatFixStatus ) => { + return { + inProgress: fixerStatus && fixerIsInProgress( fixerStatus ), + error: fixerStatus && fixerIsInError( fixerStatus ), + stale: fixerStatus && fixerStatusIsStale( fixerStatus ), + }; +}; + export const getFixerAction = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': From e1c5bf4ffaad03c7bd6381aa4dbab3a488483a22 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 15:56:21 -0800 Subject: [PATCH 007/290] Add ThreatDetailsModal to ThreatsDataViews --- .../components/threats-data-views/index.tsx | 111 ++++++++++-------- .../stories/index.stories.tsx | 4 + 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index aec1f1c3086a6..b8807b5513ed7 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,7 +1,6 @@ import { getThreatType, type Threat } from '@automattic/jetpack-scan'; import { type Action, - type ActionButton, type Field, type FieldType, type Filter, @@ -16,10 +15,10 @@ import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; import Badge from '../badge'; +import ThreatDetailsModal from '../threat-details-modal'; import ThreatFixerButton from '../threat-fixer-button'; import ThreatSeverityBadge from '../threat-severity-badge'; import { - THREAT_ACTION_FIX, THREAT_ACTION_IGNORE, THREAT_ACTION_UNIGNORE, THREAT_FIELD_AUTO_FIX, @@ -50,6 +49,7 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {Array} props.data - Threats data. * @param {Array} props.filters - Initial DataView filters. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * @param {Function} props.handleUpgradeClick - Callback function run when the upgrade button is clicked. * @param {Function} props.onFixThreats - Threat fix action callback. * @param {Function} props.onIgnoreThreats - Threat ignore action callback. * @param {Function} props.onUnignoreThreats - Threat unignore action callback. @@ -63,6 +63,7 @@ export default function ThreatsDataViews( { data, filters, onChangeSelection, + handleUpgradeClick, isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, @@ -73,12 +74,13 @@ export default function ThreatsDataViews( { data: Threat[]; filters?: Filter[]; onChangeSelection?: ( selectedItemIds: string[] ) => void; + handleUpgradeClick?: () => void; isThreatEligibleForFix?: ( threat: Threat ) => boolean; isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; onFixThreats?: ( threats: Threat[] ) => void; - onIgnoreThreats?: ActionButton< Threat >[ 'callback' ]; - onUnignoreThreats?: ActionButton< Threat >[ 'callback' ]; + onIgnoreThreats?: ( threats: Threat[] ) => void; + onUnignoreThreats?: ( threats: Threat[] ) => void; } ): JSX.Element { const baseView = { sort: { @@ -144,6 +146,19 @@ export default function ThreatsDataViews( { ...defaultLayouts.table, } ); + const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); + + const showThreatDetails = useCallback( + ( threat: Threat ) => () => { + setOpenThreat( threat ); + }, + [] + ); + + const hideThreatDetails = useCallback( () => { + setOpenThreat( null ); + }, [] ); + /** * Compute values from the provided threats data. * @@ -405,7 +420,11 @@ export default function ThreatsDataViews( { return null; } - return ; + if ( ! isThreatEligibleForFix( item ) ) { + return null; + } + + return ; }, }, ] @@ -413,7 +432,7 @@ export default function ThreatsDataViews( { ]; return result; - }, [ dataFields, plugins, themes, signatures, onFixThreats ] ); + }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatDetails ] ); /** * DataView actions - collection of operations that can be performed upon each record. @@ -423,32 +442,13 @@ export default function ThreatsDataViews( { const actions = useMemo( () => { const result: Action< Threat >[] = []; - if ( dataFields.includes( 'fixable' ) ) { - result.push( { - id: THREAT_ACTION_FIX, - label: __( 'Auto-fix', 'jetpack' ), - isPrimary: true, - supportsBulk: true, - callback: onFixThreats, - isEligible( item ) { - if ( ! onFixThreats ) { - return false; - } - if ( isThreatEligibleForFix ) { - return isThreatEligibleForFix( item ); - } - return !! item.fixable; - }, - } ); - } - if ( dataFields.includes( 'status' ) ) { result.push( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), - isPrimary: true, - isDestructive: true, - callback: onIgnoreThreats, + callback: ( items: Threat[] ) => { + showThreatDetails( items[ 0 ] )(); + }, isEligible( item ) { if ( ! onIgnoreThreats ) { return false; @@ -465,9 +465,9 @@ export default function ThreatsDataViews( { result.push( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), - isPrimary: true, - isDestructive: true, - callback: onUnignoreThreats, + callback: ( items: Threat[] ) => { + showThreatDetails( items[ 0 ] )(); + }, isEligible( item ) { if ( ! onUnignoreThreats ) { return false; @@ -483,10 +483,9 @@ export default function ThreatsDataViews( { return result; }, [ dataFields, - onFixThreats, + showThreatDetails, onIgnoreThreats, onUnignoreThreats, - isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, ] ); @@ -517,23 +516,35 @@ export default function ThreatsDataViews( { const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] ); return ( - + + } + /> + { openThreat ? ( + - } - /> + ) : null } + ); } diff --git a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx index 52afdded5ad24..c7f446d61688f 100644 --- a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx @@ -153,6 +153,7 @@ Default.args = { value: [ 'current' ], }, ], + isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => @@ -267,6 +268,7 @@ FixerStatuses.args = { value: [ 'current' ], }, ], + isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => @@ -325,4 +327,6 @@ FreeResults.args = { }, }, ], + handleUpgradeClick: () => + alert( 'Upgrade action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert }; From 9f0892e01c8d8ae661371ed1c4759d15361e3f8b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:40:18 -0800 Subject: [PATCH 008/290] Add user connection gate --- .../components/threat-details-modal/index.tsx | 109 +++++++++++------- .../stories/index.stories.tsx | 35 ++++++ .../user-connection-gate.tsx | 74 ++++++++++++ 3 files changed, 174 insertions(+), 44 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 79e14047fa3ca..99fc1a39e215d 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -8,12 +8,17 @@ import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatTechnicalDetails from './threat-technical-details'; +import UserConnectionGate from './user-connection-gate'; /** * ThreatDetailsModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. + * @param {boolean} props.isUserConnected - Whether the user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user is connecting. + * @param {Function} props.handleConnectUser - The handleConnectUser function. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. @@ -23,6 +28,10 @@ import ThreatTechnicalDetails from './threat-technical-details'; */ export default function ThreatDetailsModal( { threat, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, @@ -30,6 +39,10 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; handleUpgradeClick?: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; @@ -55,55 +68,63 @@ export default function ThreatDetailsModal( { return (
- { fixerState.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState.inProgress && ! fixerState.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
+ void } + isUserConnected={ isUserConnected } + hasConnectedOwner={ hasConnectedOwner } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + > + { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
- { !! threat.description && { threat.description } } + { !! threat.description && { threat.description } } - { !! threat.source && ( -
- -
- ) } -
+ { !! threat.source && ( +
+ +
+ ) } +
- + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> +
); diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 46b1e6a93baea..51b67773e3687 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -19,6 +19,37 @@ const Base = args => { ); }; +export const UserConnectionNeeded = Base.bind( {} ); +UserConnectionNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const ThreatResult = Base.bind( {} ); ThreatResult.args = { threat: { @@ -41,6 +72,10 @@ ThreatResult.args = { marks: {}, }, }, + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx new file mode 100644 index 0000000000000..91068e3721c0e --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -0,0 +1,74 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import React, { ReactNode } from 'react'; +import styles from './styles.module.scss'; + +const UserConnectionGate = ( { + closeModal, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + children, +}: { + closeModal: () => void; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + children: ReactNode; +} ) => { + if ( ! isUserConnected || ! hasConnectedOwner ) { + return ( + <> + { __( 'User connection needed', 'jetpack' ) } + + + { __( + 'Before Jetpack can ignore and auto-fix threats on your site, a user connection is needed.', + 'jetpack' + ) } + + } + /> + + + { __( + 'A user connection provides Jetpack the access necessary to perform these tasks.', + 'jetpack' + ) } + + + + { __( + 'Once you’ve secured a user connection, all Jetpack features will be available for use.', + 'jetpack' + ) } + + +
+ + +
+ + ); + } + + return <>{ children }; +}; + +export default UserConnectionGate; From d955bb16d890eb63473449053e48410e23bfca2a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:52:48 -0800 Subject: [PATCH 009/290] Add credentials gate --- .../threat-details-modal/credentials-gate.tsx | 71 ++++++++++++ .../components/threat-details-modal/index.tsx | 105 ++++++++++-------- .../stories/index.stories.tsx | 50 +++++++++ 3 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx new file mode 100644 index 0000000000000..e855a08133106 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -0,0 +1,71 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import React, { ReactNode } from 'react'; +import styles from './styles.module.scss'; + +const CredentialsGate = ( { + closeModal, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, + children, +}: { + closeModal: () => void; + credentials: boolean; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; + children: ReactNode; +} ) => { + if ( ! credentials || credentials.length === 0 ) { + return ( + <> + { __( 'Site credentials needed', 'jetpack' ) } + + + { __( + 'Before Jetpack can auto-fix threats on your site, it needs your server credentials.', + 'jetpack' + ) } + + } + /> + + + { __( + 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.', + 'jetpack' + ) } + + + + { __( + 'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.', + 'jetpack' + ) } + + +
+ + +
+ + ); + } + + return children; +}; + +export default CredentialsGate; diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 99fc1a39e215d..0efbca385bebc 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -4,6 +4,7 @@ import { Modal, Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo } from 'react'; import Text from '../text'; +import CredentialsGate from './credentials-gate'; import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; @@ -19,6 +20,9 @@ import UserConnectionGate from './user-connection-gate'; * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. * @param {Function} props.handleConnectUser - The handleConnectUser function. + * @param {boolean} props.credentials - Whether the user has credentials. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. + * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. @@ -32,6 +36,9 @@ export default function ThreatDetailsModal( { hasConnectedOwner, userIsConnecting, handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, @@ -43,6 +50,9 @@ export default function ThreatDetailsModal( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; + credentials: boolean; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; handleUpgradeClick?: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; @@ -75,55 +85,62 @@ export default function ThreatDetailsModal( { userIsConnecting={ userIsConnecting } handleConnectUser={ handleConnectUser } > - { fixerState.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState.inProgress && ! fixerState.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
+ void } + credentials={ credentials } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + > + { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
- { !! threat.description && { threat.description } } + { !! threat.description && { threat.description } } - { !! threat.source && ( -
- -
- ) } -
+ { !! threat.source && ( +
+ +
+ ) } +
- + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> + diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 51b67773e3687..dbcfce12809b5 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -44,12 +44,49 @@ UserConnectionNeeded.args = { isUserConnected: false, hasConnectedOwner: false, userIsConnecting: false, + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; +export const CredentialsNeeded = Base.bind( {} ); +CredentialsNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const ThreatResult = Base.bind( {} ); ThreatResult.args = { threat: { @@ -76,6 +113,9 @@ ThreatResult.args = { hasConnectedOwner: true, userIsConnecting: false, handleConnectUser: () => {}, + credentials: true, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -98,5 +138,15 @@ VulnerableExtension.args = { type: 'plugin', }, }, + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: () => {}, + credentials: true, + credentialsIsFetching: false, + credentialsRedirectUrl: '', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, handleUpgradeClick: () => {}, }; From 32a755976d613ba0e846d577cb74a584f393f94f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:56:46 -0800 Subject: [PATCH 010/290] Fix stories --- .../threat-details-modal/stories/index.stories.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 51b67773e3687..089493971f11a 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -43,7 +43,6 @@ UserConnectionNeeded.args = { }, isUserConnected: false, hasConnectedOwner: false, - userIsConnecting: false, handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, @@ -74,8 +73,6 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -98,5 +95,7 @@ VulnerableExtension.args = { type: 'plugin', }, }, + isUserConnected: true, + hasConnectedOwner: true, handleUpgradeClick: () => {}, }; From d1d546e6a9601c6f95698c1365e747a3a490e4b1 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:58:34 -0800 Subject: [PATCH 011/290] Fix stories --- .../stories/index.stories.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index dbcfce12809b5..84117c428a059 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -43,10 +43,6 @@ UserConnectionNeeded.args = { }, isUserConnected: false, hasConnectedOwner: false, - userIsConnecting: false, - credentials: false, - credentialsIsFetching: false, - credentialsRedirectUrl: '', handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, @@ -77,8 +73,6 @@ CredentialsNeeded.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: false, credentialsIsFetching: false, credentialsRedirectUrl: '', @@ -111,11 +105,7 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: true, - credentialsIsFetching: false, - credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -140,13 +130,6 @@ VulnerableExtension.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: true, - credentialsIsFetching: false, - credentialsRedirectUrl: '', - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, handleUpgradeClick: () => {}, }; From 69b07560260eba4a951a27a3c335fe9c783096e9 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 19:22:10 -0800 Subject: [PATCH 012/290] Fix credentials type --- .../components/threat-details-modal/credentials-gate.tsx | 2 +- .../components/components/threat-details-modal/index.tsx | 4 ++-- .../components/threat-details-modal/stories/index.stories.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index e855a08133106..421d71692c776 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -12,7 +12,7 @@ const CredentialsGate = ( { children, }: { closeModal: () => void; - credentials: boolean; + credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; children: ReactNode; diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 0efbca385bebc..8f570207e8331 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -20,7 +20,7 @@ import UserConnectionGate from './user-connection-gate'; * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. * @param {Function} props.handleConnectUser - The handleConnectUser function. - * @param {boolean} props.credentials - Whether the user has credentials. + * @param {object} props.credentials - The credentials. * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. @@ -50,7 +50,7 @@ export default function ThreatDetailsModal( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; - credentials: boolean; + credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; handleUpgradeClick?: () => void; diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 84117c428a059..5ad6cc99e9b35 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -105,7 +105,7 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -130,6 +130,6 @@ VulnerableExtension.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleUpgradeClick: () => {}, }; From 2f59aa878eedb4ce5695059cd555e6dc91863a4b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 11:40:37 -0800 Subject: [PATCH 013/290] Update content flow --- .../threat-details-modal/credentials-gate.tsx | 38 ++++- .../components/threat-details-modal/index.tsx | 134 ++++++++++-------- .../stories/index.stories.tsx | 76 +++++----- .../threat-details-modal/threat-actions.tsx | 34 ++++- .../threat-details-gate.tsx | 119 ++++++++++++++++ .../user-connection-gate.tsx | 39 ++++- 6 files changed, 333 insertions(+), 107 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index 421d71692c776..d0afc64db976d 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -1,22 +1,45 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; import styles from './styles.module.scss'; +/** + * CredentialsGate component + * + * @param {object} props - The component props. + * @param {Function} props.closeModal - Function to close the modal. + * @param {false | Record[]} props.credentials - The server credentials, or `false` if not set. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. + * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. + * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. + * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. + * @param {ReactNode} props.children - The child components to render if credentials are set. + * + * @return {JSX.Element} The rendered CredentialsGate component. + */ const CredentialsGate = ( { closeModal, credentials, credentialsIsFetching, credentialsRedirectUrl, + showThreatDetails, + setShowThreatDetails, children, }: { closeModal: () => void; credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; + showThreatDetails?: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! credentials || credentials.length === 0 ) { return ( <> @@ -49,9 +72,16 @@ const CredentialsGate = ( {
- +
+ { ! showThreatDetails && ( + + ) } + +
-
- ) } - + { !! threat.source && ( +
+ +
+ ) } + - + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> - - + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + showThreatDetails={ showThreatDetails } + setShowThreatDetails={ setShowThreatDetails } + fixerState={ fixerState } + /> + + + ); diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 5ad6cc99e9b35..8e02fc92de343 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -19,8 +19,8 @@ const Base = args => { ); }; -export const UserConnectionNeeded = Base.bind( {} ); -UserConnectionNeeded.args = { +export const ThreatResult = Base.bind( {} ); +ThreatResult.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -41,16 +41,39 @@ UserConnectionNeeded.args = { marks: {}, }, }, - isUserConnected: false, - hasConnectedOwner: false, - handleConnectUser: () => {}, + isUserConnected: true, + hasConnectedOwner: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; -export const CredentialsNeeded = Base.bind( {} ); -CredentialsNeeded.args = { +export const VulnerableExtension = Base.bind( {} ); +VulnerableExtension.args = { + threat: { + id: 184847701, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', + description: + 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', + fixedIn: '1.12.4', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'WP Super Cache', + slug: 'wp-super-cache', + version: '1.6.3', + type: 'plugin', + }, + }, + isUserConnected: true, + hasConnectedOwner: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + handleUpgradeClick: () => {}, +}; + +export const UserConnectionNeeded = Base.bind( {} ); +UserConnectionNeeded.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -71,18 +94,16 @@ CredentialsNeeded.args = { marks: {}, }, }, - isUserConnected: true, - hasConnectedOwner: true, - credentials: false, - credentialsIsFetching: false, - credentialsRedirectUrl: '', + isUserConnected: false, + hasConnectedOwner: false, + handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; -export const ThreatResult = Base.bind( {} ); -ThreatResult.args = { +export const CredentialsNeeded = Base.bind( {} ); +CredentialsNeeded.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -105,31 +126,10 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; - -export const VulnerableExtension = Base.bind( {} ); -VulnerableExtension.args = { - threat: { - id: 184847701, - signature: 'Vulnerable.WP.Extension', - title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', - description: - 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', - fixedIn: '1.12.4', - source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', - extension: { - name: 'WP Super Cache', - slug: 'wp-super-cache', - version: '1.6.3', - type: 'plugin', - }, - }, - isUserConnected: true, - hasConnectedOwner: true, - credentials: [ { type: 'managed', role: 'main', still_valid: true } ], - handleUpgradeClick: () => {}, -}; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index d14a2901fb4bf..fa8f14e16bb05 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useMemo } from 'react'; +import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react'; import styles from './styles.module.scss'; /** @@ -13,6 +13,8 @@ import styles from './styles.module.scss'; * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. + * @param {boolean} props.showThreatDetails - Whether to show the threat details. + * @param {Function} props.setShowThreatDetails - Function to set the showThreatDetails state. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. @@ -26,6 +28,8 @@ const ThreatActions = ( { handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, + showThreatDetails, + setShowThreatDetails, fixerState, }: { threat: Threat; @@ -33,6 +37,8 @@ const ThreatActions = ( { handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + showThreatDetails: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -52,20 +58,38 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); + const onContinueClick = useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ); + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { return null; } return (
-
+
+ { ! showThreatDetails && ( + + ) }
{ threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } @@ -75,7 +99,7 @@ const ThreatActions = ( { +
+ ) } +
+ + + + + + + + ); + } + + return <>{ children }; +}; + +export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx index 91068e3721c0e..86c0f5f7e85d2 100644 --- a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -1,15 +1,32 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; import styles from './styles.module.scss'; +/** + * UserConnectionGate component + * + * @param {object} props - The component props. + * @param {Function} props.closeModal - Function to close the modal. + * @param {boolean} props.isUserConnected - Whether the current user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. + * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. + * @param {ReactNode} props.children - The child components to render if the user is connected. + * + * @return {JSX.Element} The rendered UserConnectionGate component. + */ const UserConnectionGate = ( { closeModal, isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser, + showThreatDetails, + setShowThreatDetails, children, }: { closeModal: () => void; @@ -17,8 +34,15 @@ const UserConnectionGate = ( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; + showThreatDetails?: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! isUserConnected || ! hasConnectedOwner ) { return ( <> @@ -52,9 +76,16 @@ const UserConnectionGate = ( {
- +
+ { ! showThreatDetails && ( + + ) } + +
-
- ) } -
- + + - - void } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index 2b059a2f27dee..af524bbb60d13 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,12 +1,9 @@ -import { Text, Button } from '@automattic/jetpack-components'; import { type Threat } from '@automattic/jetpack-scan'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import React, { ReactNode, Dispatch, SetStateAction } from 'react'; -import ThreatSeverityBadge from '../threat-severity-badge'; -import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; +import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; /** @@ -58,47 +55,10 @@ const ThreatDetailsGate = ( { if ( showThreatDetails ) { return ( <> - { fixerState?.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState?.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState?.inProgress && ! fixerState?.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
- - { !! threat.description && { threat.description } } - - { !! threat.source && ( -
- -
- ) } -
- + + - - { + if ( fixerState.error ) { + return ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ); + } + if ( fixerState.stale ) { + return ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ); + } + if ( fixerState.inProgress && ! fixerState.stale ) { + return ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ); + } + return null; +}; + +export default ThreatNotices; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx b/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx new file mode 100644 index 0000000000000..7cc9af64bcdb1 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx @@ -0,0 +1,25 @@ +import { Button } from '@automattic/jetpack-components'; +import { type Threat } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import Text from '../text'; +import ThreatSeverityBadge from '../threat-severity-badge'; +import styles from './styles.module.scss'; + +const ThreatSummary = ( { threat, title }: { threat: Threat; title: string } ): JSX.Element => ( +
+
+ { title } + { !! threat.severity && } +
+ { !! threat.description && { threat.description } } + { !! threat.source && ( +
+ +
+ ) } +
+); + +export default ThreatSummary; From 05ac09ac6d2fb1a2caa107cad6d9f9589e854953 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 14:51:44 -0800 Subject: [PATCH 015/290] Separation and reorg --- .../threat-details-modal/credentials-gate.tsx | 19 +--- .../components/threat-details-modal/index.tsx | 94 +++++++++---------- .../threat-details-modal/threat-actions.tsx | 39 ++------ .../threat-details-actions.tsx | 73 ++++++++++++++ .../threat-details-gate.tsx | 54 +++-------- .../threat-fixer-modal.tsx | 69 ++++++++++++++ .../user-connection-gate.tsx | 30 ++---- 7 files changed, 225 insertions(+), 153 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-actions.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index d0afc64db976d..4ae668908cdb3 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -1,44 +1,35 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; +import React, { ReactNode, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * CredentialsGate component * * @param {object} props - The component props. - * @param {Function} props.closeModal - Function to close the modal. * @param {false | Record[]} props.credentials - The server credentials, or `false` if not set. * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. * @param {ReactNode} props.children - The child components to render if credentials are set. * * @return {JSX.Element} The rendered CredentialsGate component. */ const CredentialsGate = ( { - closeModal, credentials, credentialsIsFetching, credentialsRedirectUrl, - showThreatDetails, - setShowThreatDetails, children, }: { - closeModal: () => void; credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; - showThreatDetails?: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; + children: ReactNode; } ) => { - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick } = + useContext( ThreatDetailsModalContext ); if ( ! credentials || credentials.length === 0 ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 4b66ad74b265e..23c21e9778cdd 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,16 +1,20 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import CredentialsGate from './credentials-gate'; +import { useMemo, useState, createContext, useCallback } from 'react'; import styles from './styles.module.scss'; -import ThreatActions from './threat-actions'; import ThreatDetailsGate from './threat-details-gate'; -import ThreatFixDetails from './threat-fix-details'; -import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; -import UserConnectionGate from './user-connection-gate'; +import ThreatFixerModal from './threat-fixer-modal'; +interface ThreatDetailsModalContextType { + closeModal: () => void; + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onContinueClick: () => void; +} + +export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContextType | null >( + null +); /** * ThreatDetailsModal component @@ -81,52 +85,44 @@ export default function ThreatDetailsModal( { return (
- void } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } + void, + onShowThreatDetailsClick: useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ), + onContinueClick: useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ), + } } > - void } - isUserConnected={ isUserConnected } - hasConnectedOwner={ hasConnectedOwner } - userIsConnecting={ userIsConnecting } - handleConnectUser={ handleConnectUser } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } + - void } + - - - - - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } - fixerState={ fixerState } - /> - - - + fixerState={ fixerState } + handleUpgradeClick={ handleUpgradeClick } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + /> + +
); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index fa8f14e16bb05..a07f7355d99ac 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -1,20 +1,18 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * ThreatActions component * * @param {object} props - The component props. * @param {object} props.threat - The threat object containing action details. - * @param {Function} props.closeModal - Function to close the modal. * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Function} props.setShowThreatDetails - Function to set the showThreatDetails state. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. @@ -24,23 +22,19 @@ import styles from './styles.module.scss'; */ const ThreatActions = ( { threat, - closeModal, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, - showThreatDetails, - setShowThreatDetails, fixerState, }: { threat: Threat; - closeModal: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - showThreatDetails: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { + const { closeModal, onShowThreatDetailsClick } = useContext( ThreatDetailsModalContext ); + const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { @@ -58,15 +52,6 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - const onContinueClick = useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ); - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { return null; } @@ -74,22 +59,14 @@ const ThreatActions = ( { return (
- { ! showThreatDetails && ( - - ) } +
{ threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } @@ -99,7 +76,7 @@ const ThreatActions = ( { +
+
+ { threat.status === 'ignored' && ( + + ) } + { threat.status === 'current' && ( + <> + + + { threat.fixable && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index af524bbb60d13..f79a883351158 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,29 +1,24 @@ import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactNode, Dispatch, SetStateAction } from 'react'; -import ThreatActions from './threat-actions'; +import React, { ReactNode, useContext } from 'react'; +import ThreatDetailsActions from './threat-details-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; +import { ThreatDetailsModalContext } from '.'; /** * ThreatDetailsGate component * - * @param {object} props - The component props. - * @param {string} props.title - The title of the threat details. - * @param {Threat} props.threat - The threat object containing details. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. - * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. - * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * @param {Function} props.closeModal - Function to close the modal. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. - * @param {ReactNode} props.children - The child components to render if details are not shown. + * @param {object} props - The component props. + * @param {string} props.title - The title of the threat details. + * @param {Threat} props.threat - The threat object containing details. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer status is stale. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. + * @param {ReactNode} props.children - The child components to render if details are not shown. * * @return {JSX.Element} The rendered ThreatDetailsGate component. */ @@ -32,26 +27,16 @@ const ThreatDetailsGate = ( { threat, fixerState, handleUpgradeClick, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, - closeModal, - showThreatDetails, - setShowThreatDetails, children, }: { title: string; threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleUpgradeClick: () => void; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - closeModal: () => void; - showThreatDetails: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const { showThreatDetails } = useContext( ThreatDetailsModalContext ); + if ( showThreatDetails ) { return ( <> @@ -59,16 +44,7 @@ const ThreatDetailsGate = ( { - + ); } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx new file mode 100644 index 0000000000000..7afbaa5129abc --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx @@ -0,0 +1,69 @@ +import { type Threat } from '@automattic/jetpack-scan'; +import CredentialsGate from './credentials-gate'; +import ThreatActions from './threat-actions'; +import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; +import ThreatSummary from './threat-summary'; +import ThreatTechnicalDetails from './threat-technical-details'; +import UserConnectionGate from './user-connection-gate'; + +const ThreatFixerModal = ( { + title, + threat, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, + fixerState, + handleUpgradeClick, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, +}: { + title: string; + threat: Threat; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + credentials: false | Record< string, unknown >[]; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; + handleUpgradeClick: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; +} ) => { + return ( + + + + + + + + + + ); +}; + +export default ThreatFixerModal; diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx index 86c0f5f7e85d2..c5336b53618f8 100644 --- a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -1,47 +1,37 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; +import React, { ReactNode, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * UserConnectionGate component * - * @param {object} props - The component props. - * @param {Function} props.closeModal - Function to close the modal. - * @param {boolean} props.isUserConnected - Whether the current user is connected. - * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. - * @param {ReactNode} props.children - The child components to render if the user is connected. + * @param {object} props - The component props. + * @param {boolean} props.isUserConnected - Whether the current user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {ReactNode} props.children - The child components to render if the user is connected. * * @return {JSX.Element} The rendered UserConnectionGate component. */ const UserConnectionGate = ( { - closeModal, isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser, - showThreatDetails, - setShowThreatDetails, children, }: { - closeModal: () => void; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; - showThreatDetails?: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick } = + useContext( ThreatDetailsModalContext ); if ( ! isUserConnected || ! hasConnectedOwner ) { return ( From 1f25735889b3a316a8cd32a7b451f0e26e72ae87 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 14:59:59 -0800 Subject: [PATCH 016/290] Improve --- .../components/components/threat-details-modal/index.tsx | 3 +-- .../threat-details-modal/threat-fix-details.tsx | 2 +- .../threat-details-modal/threat-fixer-modal.tsx | 8 +++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 23c21e9778cdd..a2e2de9ad972a 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -108,6 +108,7 @@ export default function ThreatDetailsModal( { void; + handleUpgradeClick?: () => void; } ): JSX.Element => { const title = useMemo( () => { if ( threat.status === 'fixed' ) { diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx index 7afbaa5129abc..a99f3b99e1b2e 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx @@ -10,6 +10,7 @@ import UserConnectionGate from './user-connection-gate'; const ThreatFixerModal = ( { title, threat, + fixerState, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -17,14 +18,13 @@ const ThreatFixerModal = ( { credentials, credentialsIsFetching, credentialsRedirectUrl, - fixerState, - handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, }: { title: string; threat: Threat; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -32,8 +32,6 @@ const ThreatFixerModal = ( { credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleUpgradeClick: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; @@ -52,7 +50,7 @@ const ThreatFixerModal = ( { > - + Date: Fri, 15 Nov 2024 15:12:08 -0800 Subject: [PATCH 017/290] Remove redundant code --- .../components/threat-details-modal/index.tsx | 4 +- .../threat-details-modal/threat-actions.tsx | 53 +++++++------- .../threat-details-actions.tsx | 73 ------------------- .../threat-details-gate.tsx | 4 +- ...-modal.tsx => threat-fix-confirmation.tsx} | 6 +- 5 files changed, 35 insertions(+), 105 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-actions.tsx rename projects/js-packages/components/components/threat-details-modal/{threat-fixer-modal.tsx => threat-fix-confirmation.tsx} (96%) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index a2e2de9ad972a..842458b2a42c4 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { useMemo, useState, createContext, useCallback } from 'react'; import styles from './styles.module.scss'; import ThreatDetailsGate from './threat-details-gate'; -import ThreatFixerModal from './threat-fixer-modal'; +import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatDetailsModalContextType { closeModal: () => void; showThreatDetails: boolean; @@ -105,7 +105,7 @@ export default function ThreatDetailsModal( { fixerState={ fixerState } handleUpgradeClick={ handleUpgradeClick } > - void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { - const { closeModal, onShowThreatDetailsClick } = useContext( ThreatDetailsModalContext ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + useContext( ThreatDetailsModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -52,41 +53,43 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - return (
- + { ! showThreatDetails && ( + + ) }
- { threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } { threat.status === 'current' && ( <> - { handleIgnoreThreatClick && ( - - ) } - { threat.fixable && handleFixThreatClick && ( + + { threat.fixable && ( -
-
- { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> - - - { threat.fixable && ( - - ) } - - ) } -
-
- ); -}; - -export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index f79a883351158..1b8eec457358d 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,6 +1,6 @@ import { type Threat } from '@automattic/jetpack-scan'; import React, { ReactNode, useContext } from 'react'; -import ThreatDetailsActions from './threat-details-actions'; +import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; @@ -44,7 +44,7 @@ const ThreatDetailsGate = ( { - + ); } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx rename to projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index a99f3b99e1b2e..493d376735912 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -7,7 +7,7 @@ import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; -const ThreatFixerModal = ( { +const ThreatFixConfirmation = ( { title, threat, fixerState, @@ -54,14 +54,14 @@ const ThreatFixerModal = ( { ); }; -export default ThreatFixerModal; +export default ThreatFixConfirmation; From e7ff7318ff3948d3018c9496cdaaaac9ebf1b0fb Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 15:16:31 -0800 Subject: [PATCH 018/290] Adjust modal content --- .../components/threat-details-modal/index.tsx | 5 ++++- .../threat-details-modal/threat-fix-confirmation.tsx | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 842458b2a42c4..a8ad37feefad3 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -21,6 +21,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex * * @param {object} props - The props. * @param {object} props.threat - The threat. + * @param {boolean} props.showDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -37,6 +38,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, + showDetails = true, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -51,6 +53,7 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; + showDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -64,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( true ); + const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index 493d376735912..ce31f3ec13ea7 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -3,12 +3,12 @@ import CredentialsGate from './credentials-gate'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; +// import ThreatSummary from './threat-summary'; +// import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { - title, + // title, threat, fixerState, isUserConnected, @@ -48,10 +48,11 @@ const ThreatFixConfirmation = ( { credentialsIsFetching={ credentialsIsFetching } credentialsRedirectUrl={ credentialsRedirectUrl } > + { /* TODO: Determine what we want to display here */ } - + { /* */ } - + { /* */ } Date: Fri, 15 Nov 2024 15:35:17 -0800 Subject: [PATCH 019/290] Fix implementation and stories --- .../components/threats-data-views/index.tsx | 43 +- .../stories/index.stories.tsx | 413 +++++++++++++++--- 2 files changed, 399 insertions(+), 57 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index b8807b5513ed7..ffb5f861b3608 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -56,6 +56,13 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing. * @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring. * @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring. + * @param {boolean} props.isUserConnected - Whether the user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user is connecting. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {object[]} props.credentials - The credentials. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. + * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * * @return {JSX.Element} The ThreatsDataViews component. */ @@ -64,23 +71,37 @@ export default function ThreatsDataViews( { filters, onChangeSelection, handleUpgradeClick, - isThreatEligibleForFix, - isThreatEligibleForIgnore, - isThreatEligibleForUnignore, onFixThreats, onIgnoreThreats, onUnignoreThreats, + isThreatEligibleForFix, + isThreatEligibleForIgnore, + isThreatEligibleForUnignore, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, }: { data: Threat[]; filters?: Filter[]; onChangeSelection?: ( selectedItemIds: string[] ) => void; handleUpgradeClick?: () => void; - isThreatEligibleForFix?: ( threat: Threat ) => boolean; - isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; - isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; onFixThreats?: ( threats: Threat[] ) => void; onIgnoreThreats?: ( threats: Threat[] ) => void; onUnignoreThreats?: ( threats: Threat[] ) => void; + isThreatEligibleForFix?: ( threat: Threat ) => boolean; + isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; + isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + credentials: false | Record< string, unknown >[]; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; } ): JSX.Element { const baseView = { sort: { @@ -538,11 +559,19 @@ export default function ThreatsDataViews( { { openThreat ? ( ) : null } diff --git a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx index c7f446d61688f..e3de4146a59d5 100644 --- a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx @@ -18,6 +18,129 @@ export default { ], }; +export const FixerStatuses = args => ; +FixerStatuses.args = { + data: [ + { + id: 13216959, + signature: 'Vulnerable.WP.Core', + title: 'Vulnerable WordPress Version (6.4.3)', + description: 'This threat has an auto-fixer available. ', + firstDetected: '2024-07-15T21:56:50.000Z', + severity: 4, + fixer: null, + fixedOn: '2024-07-15T22:01:42.000Z', + status: 'current', + fixable: { fixer: 'update', target: '6.4.4', extensionStatus: 'inactive' }, + version: '6.4.3', + source: '', + }, + { + id: 12345678910, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: Example Plugin (version 1.2.3)', + description: 'This threat has an in-progress auto-fixer.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '1.2.4', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date().toISOString() }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Plugin', + slug: 'example-plugin', + version: '1.2.3', + type: 'plugin', + }, + }, + { + id: 12345678911, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Theme: Example Theme (version 2.2.2)', + description: 'This threat has an in-progress auto-fixer that is taking too long.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '2.22.22', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Theme', + slug: 'example-theme', + version: '2.2.2', + type: 'theme', + }, + }, + { + id: 12345678912, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Theme: Example Theme II (version 3.3.3)', + description: 'This threat has a fixer with an error status.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '3.4.5', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'error', error: 'error' }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Theme II', + slug: 'example-theme-2', + version: '3.3.3', + type: 'theme', + }, + }, + { + id: 185868972, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + description: 'This threat has no auto-fixer available.', + firstDetected: '2024-10-07T20:40:15.000Z', + fixedIn: null, + severity: 1, + fixable: false, + status: 'current', + filename: '/var/www/html/wp-content/uploads/jptt_eicar.php', + context: { + '6': 'echo << + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsIsFetching: false, + credentialsRedirectUrl: '', + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + export const Default = args => ; Default.args = { data: [ @@ -153,6 +276,11 @@ Default.args = { value: [ 'current' ], }, ], + isUserConnected: true, + hasConnectedOwner: true, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert @@ -165,85 +293,199 @@ Default.args = { ), }; -export const FixerStatuses = args => ; -FixerStatuses.args = { +export const UserConnectedRequired = args => ; +UserConnectedRequired.args = { data: [ { - id: 13216959, - signature: 'Vulnerable.WP.Core', - title: 'Vulnerable WordPress Version (6.4.3)', - description: 'This threat has an auto-fixer available. ', - firstDetected: '2024-07-15T21:56:50.000Z', + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + +export const CredentialsRequired = args => ; +CredentialsRequired.args = { + data: [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << true, onFixThreats: () => - alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onUnignoreThreats: () => From 00511f6b38fd83566ac857b258054a8326bb2a56 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 10:02:43 -0800 Subject: [PATCH 020/290] Update flag --- .../components/components/threat-details-modal/index.tsx | 8 ++++---- .../components/components/threats-data-views/index.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index a8ad37feefad3..6705b5eb7e635 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -21,7 +21,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.showDetails - Whether to show the details. + * @param {boolean} props.skipThreatDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -38,7 +38,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, - showDetails = true, + skipThreatDetails = false, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -53,7 +53,7 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; - showDetails?: boolean; + skipThreatDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -67,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); + const [ showThreatDetails, setShowThreatDetails ] = useState( skipThreatDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index ffb5f861b3608..8c580941c77e2 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -559,7 +559,7 @@ export default function ThreatsDataViews( { { openThreat ? ( Date: Mon, 18 Nov 2024 10:18:53 -0800 Subject: [PATCH 021/290] Fix display logic --- .../components/components/threat-details-modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 6705b5eb7e635..fd1627cac1daa 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -67,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( skipThreatDetails ); + const [ showThreatDetails, setShowThreatDetails ] = useState( ! skipThreatDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); From 8c0991716abaa5c787a13d838c8e0e271a7595e8 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 11:16:11 -0800 Subject: [PATCH 022/290] Add showThreatDetails action to threat title --- .../components/threats-data-views/index.tsx | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 8c580941c77e2..9990a596acff6 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,3 +1,4 @@ +import { Button } from '@automattic/jetpack-components'; import { getThreatType, type Threat } from '@automattic/jetpack-scan'; import { type Action, @@ -168,16 +169,20 @@ export default function ThreatsDataViews( { } ); const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); + const [ skipThreatDetails, setSkipThreatDetails ] = useState< boolean >( false ); const showThreatDetails = useCallback( - ( threat: Threat ) => () => { - setOpenThreat( threat ); - }, + ( threat: Threat, skipDetails = false ) => + () => { + setOpenThreat( threat ); + setSkipThreatDetails( skipDetails ); + }, [] ); const hideThreatDetails = useCallback( () => { setOpenThreat( null ); + setSkipThreatDetails( false ); }, [] ); /** @@ -262,7 +267,15 @@ export default function ThreatsDataViews( { enableGlobalSearch: true, enableHiding: false, render: ( { item }: { item: Threat } ) => ( -
{ item.title }
+ ), }, { @@ -445,7 +458,9 @@ export default function ThreatsDataViews( { return null; } - return ; + return ( + + ); }, }, ] @@ -468,7 +483,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ] )(); + showThreatDetails( items[ 0 ], true )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -487,7 +502,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ] )(); + showThreatDetails( items[ 0 ], true )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -559,7 +574,7 @@ export default function ThreatsDataViews( { { openThreat ? ( Date: Mon, 18 Nov 2024 11:50:49 -0800 Subject: [PATCH 023/290] Move context provided a component level up --- .../threat-details-modal/credentials-gate.tsx | 5 +- .../components/threat-details-modal/index.tsx | 68 ++++----------- .../threat-details-modal/threat-actions.tsx | 4 +- .../threat-details-gate.tsx | 4 +- .../threat-fix-confirmation.tsx | 10 +-- .../user-connection-gate.tsx | 4 +- .../components/threats-data-views/index.tsx | 86 ++++++++++++------- 7 files changed, 89 insertions(+), 92 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index 4ae668908cdb3..966982b80b76a 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -2,9 +2,8 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import React, { ReactNode, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; -import { ThreatDetailsModalContext } from '.'; - /** * CredentialsGate component * @@ -29,7 +28,7 @@ const CredentialsGate = ( { children: ReactNode; } ) => { const { closeModal, showThreatDetails, onShowThreatDetailsClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); if ( ! credentials || credentials.length === 0 ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index fd1627cac1daa..95804111f8235 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,27 +1,16 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, createContext, useCallback } from 'react'; +import { useMemo } from 'react'; import styles from './styles.module.scss'; import ThreatDetailsGate from './threat-details-gate'; import ThreatFixConfirmation from './threat-fix-confirmation'; -interface ThreatDetailsModalContextType { - closeModal: () => void; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onContinueClick: () => void; -} - -export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContextType | null >( - null -); /** * ThreatDetailsModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.skipThreatDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -38,7 +27,6 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, - skipThreatDetails = false, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -53,7 +41,6 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; - skipThreatDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -67,8 +54,6 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( ! skipThreatDetails ); - const fixerState = useMemo( () => { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); @@ -88,43 +73,28 @@ export default function ThreatDetailsModal( { return (
- void, - onShowThreatDetailsClick: useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ), - onContinueClick: useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ), - } } + - - - - + isUserConnected={ isUserConnected } + hasConnectedOwner={ hasConnectedOwner } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + credentials={ credentials } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + /> +
); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index 3a6d3be1d580f..2e2d6f3d51cc8 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; -import { ThreatDetailsModalContext } from '.'; /** * ThreatActions component @@ -34,7 +34,7 @@ const ThreatActions = ( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index 1b8eec457358d..5b37d2ce5ac47 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,11 +1,11 @@ import { type Threat } from '@automattic/jetpack-scan'; import React, { ReactNode, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; -import { ThreatDetailsModalContext } from '.'; /** * ThreatDetailsGate component @@ -35,7 +35,7 @@ const ThreatDetailsGate = ( { handleUpgradeClick: () => void; children: ReactNode; } ) => { - const { showThreatDetails } = useContext( ThreatDetailsModalContext ); + const { showThreatDetails } = useContext( ThreatModalContext ); if ( showThreatDetails ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index ce31f3ec13ea7..c5c9a0f137399 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -3,12 +3,12 @@ import CredentialsGate from './credentials-gate'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; -// import ThreatSummary from './threat-summary'; -// import ThreatTechnicalDetails from './threat-technical-details'; +import ThreatSummary from './threat-summary'; +import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { - // title, + title, threat, fixerState, isUserConnected, @@ -50,9 +50,9 @@ const ThreatFixConfirmation = ( { > { /* TODO: Determine what we want to display here */ } - { /* */ } + - { /* */ } + { const { closeModal, showThreatDetails, onShowThreatDetailsClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); if ( ! isUserConnected || ! hasConnectedOwner ) { return ( diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 9990a596acff6..4e045c49f7bea 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -14,7 +14,7 @@ import { import { dateI18n } from '@wordpress/date'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState, createContext } from 'react'; import Badge from '../badge'; import ThreatDetailsModal from '../threat-details-modal'; import ThreatFixerButton from '../threat-fixer-button'; @@ -43,6 +43,15 @@ import { import styles from './styles.module.scss'; import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-control'; +interface ThreatModalContextType { + closeModal: () => void; + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onHideThreatDetailsClick: () => void; +} + +export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); + /** * DataViews component for displaying security threats. * @@ -169,22 +178,32 @@ export default function ThreatsDataViews( { } ); const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); - const [ skipThreatDetails, setSkipThreatDetails ] = useState< boolean >( false ); + const [ showThreatDetails, setShowThreatDetails ] = useState< boolean >( true ); - const showThreatDetails = useCallback( - ( threat: Threat, skipDetails = false ) => + const showThreatModal = useCallback( + ( threat: Threat, showDetails = true ) => () => { setOpenThreat( threat ); - setSkipThreatDetails( skipDetails ); + setShowThreatDetails( showDetails ); }, [] ); - const hideThreatDetails = useCallback( () => { + const hideThreatModal = useCallback( () => { setOpenThreat( null ); - setSkipThreatDetails( false ); + setShowThreatDetails( true ); }, [] ); + const onShowThreatDetails = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + + const onHideThreatDetails = useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ); + /** * Compute values from the provided threats data. * @@ -272,7 +291,7 @@ export default function ThreatsDataViews( { variant="link" size="small" weight="regular" - onClick={ showThreatDetails( item ) } + onClick={ showThreatModal( item ) } > { item.title } @@ -459,7 +478,7 @@ export default function ThreatsDataViews( { } return ( - + ); }, }, @@ -468,7 +487,7 @@ export default function ThreatsDataViews( { ]; return result; - }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatDetails ] ); + }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatModal ] ); /** * DataView actions - collection of operations that can be performed upon each record. @@ -483,7 +502,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ], true )(); + showThreatModal( items[ 0 ], false )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -502,7 +521,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ], true )(); + showThreatModal( items[ 0 ], false )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -519,7 +538,7 @@ export default function ThreatsDataViews( { return result; }, [ dataFields, - showThreatDetails, + showThreatModal, onIgnoreThreats, onUnignoreThreats, isThreatEligibleForIgnore, @@ -572,22 +591,31 @@ export default function ThreatsDataViews( { } /> { openThreat ? ( - + + + ) : null } ); From e6ca520267434f18f36d3b058fc1674fff9bacd3 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:41:43 -0800 Subject: [PATCH 024/290] Reorg and fix stories --- .../add-components-threat-details-modal | 2 +- .../credentials-gate.tsx | 4 +- .../index.tsx | 71 +++++++++++++------ .../stories/index.stories.tsx | 23 ++++-- .../styles.module.scss | 0 .../threat-actions.tsx | 12 ++-- .../threat-details-gate.tsx | 2 +- .../threat-fix-confirmation.tsx | 0 .../threat-fix-details.tsx | 0 .../threat-notice.tsx | 0 .../threat-summary.tsx | 0 .../threat-technical-details.tsx | 0 .../user-connection-gate.tsx | 4 +- .../components/threats-data-views/index.tsx | 56 ++++++--------- .../contextualized-connection/index.jsx | 4 +- .../prompts/site-type/index.jsx | 4 +- 16 files changed, 103 insertions(+), 79 deletions(-) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/credentials-gate.tsx (96%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/index.tsx (60%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/stories/index.stories.tsx (86%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/styles.module.scss (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-actions.tsx (92%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-details-gate.tsx (97%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-confirmation.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-notice.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-summary.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-technical-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/user-connection-gate.tsx (96%) diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal index 3caa1f5d56878..f1a9596ce3b78 100644 --- a/projects/js-packages/components/changelog/add-components-threat-details-modal +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds ThreatModal component and stories diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx rename to projects/js-packages/components/components/threat-modal/credentials-gate.tsx index 966982b80b76a..6d950da64ac63 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -2,8 +2,8 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import React, { ReactNode, useContext } from 'react'; -import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * CredentialsGate component * @@ -65,7 +65,7 @@ const CredentialsGate = ( {
{ ! showThreatDetails && ( ) } - { isOpen ? : null } + + { isOpen ? ( + + ) : null }
); }; diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/styles.module.scss rename to projects/js-packages/components/components/threat-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx similarity index 92% rename from projects/js-packages/components/components/threat-details-modal/threat-actions.tsx rename to projects/js-packages/components/components/threat-modal/threat-actions.tsx index 2e2d6f3d51cc8..1e697deca85a6 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; -import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatActions component @@ -33,7 +33,7 @@ const ThreatActions = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -58,7 +58,7 @@ const ThreatActions = ( {
{ ! showThreatDetails && ( ) } @@ -80,7 +80,7 @@ const ThreatActions = ( { ) } diff --git a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx index 7424fe8dc5b51..c6137d533c95d 100644 --- a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx +++ b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx @@ -42,7 +42,7 @@ const SiteTypeQuestionComponent = props => { } }, [ stateStepSlug, updatingStep, updateRecommendationsStep, addViewedRecommendation ] ); - const onContinueClick = useCallback( () => { + const onHideThreatDetailsClick = useCallback( () => { saveRecommendationsData(); analytics.tracks.recordEvent( 'jetpack_recommendations_site_type_answered', answers ); }, [ answers, saveRecommendationsData ] ); @@ -75,7 +75,7 @@ const SiteTypeQuestionComponent = props => { ) } />
-
From 924768ab7b2e2730fe5f033bc55949da710a6051 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:43:37 -0800 Subject: [PATCH 025/290] changelog --- .../add-components-threats-data-views-modal-integration | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration new file mode 100644 index 0000000000000..96ed49ef842ed --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Adds ThreatModal and integrates with ThreatsDataViews From abde93a8f2c0fb7bcae572259e67469881c03459 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:46:10 -0800 Subject: [PATCH 026/290] Remove unintended changes --- .../client/components/contextualized-connection/index.jsx | 4 ++-- .../_inc/client/recommendations/prompts/site-type/index.jsx | 4 ++-- .../add-components-threats-data-views-modal-integration | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx b/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx index d56414c4f2d3e..308dd36ebd5f0 100644 --- a/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx +++ b/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx @@ -35,7 +35,7 @@ const ContextualizedConnection = props => { setHasSeenWCConnectionModal(); }, [ setHasSeenWCConnectionModal ] ); - const onHideThreatDetailsClick = useCallback( () => { + const onContinueClick = useCallback( () => { analytics.tracks.recordJetpackClick( 'contextualized_connection_continue_button' ); }, [] ); @@ -54,7 +54,7 @@ const ContextualizedConnection = props => { className="jp-contextualized-connection__button" label={ __( 'Continue to Jetpack', 'jetpack' ) } href={ redirectTo } - onClick={ onHideThreatDetailsClick } + onClick={ onContinueClick } > { __( 'Continue to Jetpack', 'jetpack' ) } diff --git a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx index c6137d533c95d..7424fe8dc5b51 100644 --- a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx +++ b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx @@ -42,7 +42,7 @@ const SiteTypeQuestionComponent = props => { } }, [ stateStepSlug, updatingStep, updateRecommendationsStep, addViewedRecommendation ] ); - const onHideThreatDetailsClick = useCallback( () => { + const onContinueClick = useCallback( () => { saveRecommendationsData(); analytics.tracks.recordEvent( 'jetpack_recommendations_site_type_answered', answers ); }, [ answers, saveRecommendationsData ] ); @@ -75,7 +75,7 @@ const SiteTypeQuestionComponent = props => { ) } />
-
diff --git a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration deleted file mode 100644 index 96ed49ef842ed..0000000000000 --- a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Adds ThreatModal and integrates with ThreatsDataViews From fddb7f93f1cb052e0083b7f8ce3eb1e5b13e436c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:48:55 -0800 Subject: [PATCH 027/290] Generalize component name --- .../changelog/add-components-threat-details-modal | 2 +- .../components/components/threat-details-modal/index.tsx | 6 +++--- .../threat-details-modal/stories/index.stories.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal index 3caa1f5d56878..f1a9596ce3b78 100644 --- a/projects/js-packages/components/changelog/add-components-threat-details-modal +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds ThreatModal component and stories diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 79e14047fa3ca..b2caf3b09dd52 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -10,7 +10,7 @@ import ThreatFixDetails from './threat-fix-details'; import ThreatTechnicalDetails from './threat-technical-details'; /** - * ThreatDetailsModal component + * ThreatModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. @@ -19,9 +19,9 @@ import ThreatTechnicalDetails from './threat-technical-details'; * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * - * @return {JSX.Element} The threat details modal. + * @return {JSX.Element} The threat modal. */ -export default function ThreatDetailsModal( { +export default function ThreatModal( { threat, handleUpgradeClick, handleFixThreatClick, diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 46b1e6a93baea..41f6b83388f3a 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -1,10 +1,10 @@ import { useCallback, useState } from 'react'; import Button from '../../button/index.js'; -import ThreatDetailsModal from '../index.js'; +import ThreatModal from '../index.js'; export default { - title: 'JS Packages/Components/Threat Details Modal', - component: ThreatDetailsModal, + title: 'JS Packages/Components/Threat Modal', + component: ThreatModal, }; const Base = args => { @@ -14,7 +14,7 @@ const Base = args => { return (
- { isOpen ? : null } + { isOpen ? : null }
); }; From d3aaff751f7b78e80d1ea2f58eecf26e8646a61f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:51:22 -0800 Subject: [PATCH 028/290] Updates after renaming --- .../components/{threat-details-modal => threat-modal}/index.tsx | 0 .../stories/index.stories.tsx | 2 +- .../{threat-details-modal => threat-modal}/styles.module.scss | 0 .../{threat-details-modal => threat-modal}/threat-actions.tsx | 0 .../threat-fix-details.tsx | 0 .../threat-technical-details.tsx | 0 6 files changed, 1 insertion(+), 1 deletion(-) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/index.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/stories/index.stories.tsx (96%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/styles.module.scss (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-actions.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-technical-details.tsx (100%) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/index.tsx rename to projects/js-packages/components/components/threat-modal/index.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx rename to projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 41f6b83388f3a..e1e2f954c6184 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -13,7 +13,7 @@ const Base = args => { const onRequestClose = useCallback( () => setIsOpen( false ), [] ); return (
- + { isOpen ? : null }
); diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/styles.module.scss rename to projects/js-packages/components/components/threat-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-actions.tsx rename to projects/js-packages/components/components/threat-modal/threat-actions.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx rename to projects/js-packages/components/components/threat-modal/threat-fix-details.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx rename to projects/js-packages/components/components/threat-modal/threat-technical-details.tsx From 3851638f4a3563b6b35bc25ae60f17b9ca5745bf Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:53:25 -0800 Subject: [PATCH 029/290] Ensure close button exists for vulns modal --- .../components/components/threat-modal/threat-actions.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index d14a2901fb4bf..ee1fad99cd075 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -52,10 +52,6 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - return (
From 0c7099ed41abb5651a88299992c5b06051c6034f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 11:51:46 -0800 Subject: [PATCH 030/290] changelog --- .../changelog/update-components-threat-details-modal-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/components/changelog/update-components-threat-details-modal-flow diff --git a/projects/js-packages/components/changelog/update-components-threat-details-modal-flow b/projects/js-packages/components/changelog/update-components-threat-details-modal-flow new file mode 100644 index 0000000000000..988212c757caf --- /dev/null +++ b/projects/js-packages/components/changelog/update-components-threat-details-modal-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates ThreatModal flow From 670837869f3fa7f78879ae2ececc41e525e821c2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 12:10:03 -0800 Subject: [PATCH 031/290] Updates/fixes --- .../threat-modal/credentials-gate.tsx | 1 + .../components/threat-modal/index.tsx | 34 ++++++++----------- .../threat-modal/stories/index.stories.tsx | 15 +++++++- .../threat-modal/threat-actions.tsx | 12 ++++--- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx index dfd11f2b79e6e..893d3b2db8760 100644 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -37,6 +37,7 @@ const CredentialsGate = ( { <> { __( diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 8726727e37ff7..e62510b03ba65 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,7 +1,7 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, createContext, useCallback } from 'react'; +import { useMemo, createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; @@ -11,7 +11,7 @@ interface ThreatModalContextType { closeModal: () => void; showThreatDetails: boolean; onShowThreatDetailsClick: () => void; - onContinueClick: () => void; + onHideThreatDetailsClick: () => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -21,7 +21,6 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.showDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -33,12 +32,13 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. - * + * @param {boolean} props.showThreatDetails - Whether to show the threat details. + * @param {Function} props.onShowThreatDetailsClick - The onShowThreatDetailsClick function. + * @param {Function} props.onHideThreatDetailsClick - The onHideThreatDetailsClick function. * @return {JSX.Element} The threat modal. */ export default function ThreatModal( { threat, - showDetails = true, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -50,10 +50,12 @@ export default function ThreatModal( { handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, + showThreatDetails, + onShowThreatDetailsClick, + onHideThreatDetailsClick, ...modalProps }: { threat: Threat; - showDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -65,13 +67,13 @@ export default function ThreatModal( { handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - [ key: string ]: unknown; -} ): JSX.Element { + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onHideThreatDetailsClick: () => void; +} & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; - const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); - const fixerState = useMemo( () => { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); @@ -109,15 +111,9 @@ export default function ThreatModal( { void, - onShowThreatDetailsClick: useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ), - onContinueClick: useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ), + closeModal: modalProps.onRequestClose, + onShowThreatDetailsClick, + onHideThreatDetailsClick, } } > { const [ isOpen, setIsOpen ] = useState( false ); const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + + const [ showThreatDetails, setShowThreatDetails ] = useState( true ); + const onShowThreatDetails = useCallback( () => setShowThreatDetails( true ), [] ); + const onHideThreatDetails = useCallback( () => setShowThreatDetails( false ), [] ); + return (
- { isOpen ? : null } + { isOpen ? ( + + ) : null }
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index be7e478cfb3f9..ae7c4958a4c70 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -33,7 +33,7 @@ const ThreatActions = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -53,6 +53,10 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); + if ( ! threat.status ) { + return null; + } + return (
{ ! showThreatDetails && ( @@ -65,7 +69,7 @@ const ThreatActions = ( { @@ -75,7 +79,7 @@ const ThreatActions = ( { ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx index 01f01b93cbcd6..a2af5a645b903 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx @@ -1,4 +1,4 @@ -import { Threat, getFixerMessage } from '@automattic/jetpack-scan'; +import { Threat, getFixerDescription } from '@automattic/jetpack-scan'; import { __, sprintf } from '@wordpress/i18n'; import React, { useMemo } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; @@ -43,7 +43,7 @@ const ThreatFixDetails = ( { ); } // The threat has an auto-fix available. - return getFixerMessage( threat ); + return getFixerDescription( threat ); }, [ threat ] ); if ( ! threat.fixable && ! threat.fixedIn ) { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 8b186b065bb33..1ce3b30ada3e0 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -55,11 +55,50 @@ export const getFixerAction = ( threat: Threat ) => { case 'rollback': return __( 'Replace', 'jetpack-scan' ); default: - return __( 'Fix', 'jetpack-scan' ); + return __( 'Auto-fix', 'jetpack-scan' ); } }; -export const getFixerMessage = ( threat: Threat ) => { +export const getDetailedFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + return __( 'Delete file', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Delete plugin from site', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Delete theme from site', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.extension?.type === 'plugin' ) { + return __( 'Update plugin to newer version', 'jetpack-scan' ); + } + if ( threat.extension?.type === 'theme' ) { + return __( 'Update theme to newer version', 'jetpack-scan' ); + } + return __( 'Update', 'jetpack-scan' ); + break; + case 'replace': + case 'rollback': + if ( threat.filename ) { + return __( 'Replace from backup', 'jetpack-scan' ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( 'Replace default salts', 'jetpack-scan' ); + } + break; + default: + return __( 'Auto-fix', 'jetpack-scan' ); + } +}; + +export const getFixerDescription = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': if ( threat.filename ) { From 4e50a2611871fc0fcf9a2d610ec66148d6a92a65 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 13:51:00 -0800 Subject: [PATCH 033/290] changelog --- .../add-components-threats-data-views-modal-integration | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration b/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration new file mode 100644 index 0000000000000..ccbcd7068d542 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds utility for retrieving a detailed action description From 4709a1c13ceecd7510791107c63d0329749007ff Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 14:02:56 -0800 Subject: [PATCH 034/290] Add detailed action --- .../components/threat-fixer-button/index.tsx | 4 +- .../threat-modal/threat-actions.tsx | 6 +-- .../threat-modal/threat-fix-confirmation.tsx | 2 - .../threat-modal/threat-fix-details.tsx | 4 +- projects/js-packages/scan/src/utils/index.ts | 43 ++++++++++++++++++- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index 11520c139ceee..24d77053ef4f8 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -3,7 +3,7 @@ import { type Threat, getFixerState, getFixerAction, - getFixerMessage, + getFixerDescription, } from '@automattic/jetpack-scan'; import { Tooltip } from '@wordpress/components'; import { useCallback, useMemo } from '@wordpress/element'; @@ -50,7 +50,7 @@ export default function ThreatFixerButton( { return __( 'An auto-fixer is in progress.', 'jetpack' ); } - return getFixerMessage( threat ); + return getFixerDescription( threat ); }, [ threat, fixerState ] ); const buttonText = useMemo( () => { diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index ae7c4958a4c70..1927b6da1523f 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,5 +1,5 @@ import { Button } from '@automattic/jetpack-components'; -import { Threat, getFixerAction } from '@automattic/jetpack-scan'; +import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; import styles from './styles.module.scss'; @@ -36,7 +36,7 @@ const ThreatActions = ( { const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); - const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { handleFixThreatClick?.( [ threat ] ); @@ -92,7 +92,7 @@ const ThreatActions = ( { > { fixerState.error || fixerState.stale ? __( 'Retry fix', 'jetpack' ) - : fixerAction } + : detailedFixerAction } ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index 81de9c3be18c4..c50b9e44231eb 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -4,7 +4,6 @@ import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { @@ -48,7 +47,6 @@ const ThreatFixConfirmation = ( { - { case 'rollback': return __( 'Replace', 'jetpack-scan' ); default: - return __( 'Fix', 'jetpack-scan' ); + return __( 'Auto-fix', 'jetpack-scan' ); } }; -export const getFixerMessage = ( threat: Threat ) => { +export const getDetailedFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + return __( 'Delete file', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Delete plugin from site', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Delete theme from site', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.extension?.type === 'plugin' ) { + return __( 'Update plugin to newer version', 'jetpack-scan' ); + } + if ( threat.extension?.type === 'theme' ) { + return __( 'Update theme to newer version', 'jetpack-scan' ); + } + return __( 'Update', 'jetpack-scan' ); + break; + case 'replace': + case 'rollback': + if ( threat.filename ) { + return __( 'Replace from backup', 'jetpack-scan' ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( 'Replace default salts', 'jetpack-scan' ); + } + break; + default: + return __( 'Auto-fix', 'jetpack-scan' ); + } +}; + +export const getFixerDescription = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': if ( threat.filename ) { From 799aa0bc84b3a28ab6d920565680b79e3384280f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 14:03:33 -0800 Subject: [PATCH 035/290] changelog --- .../changelog/update-components-threat-details-modal-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/scan/changelog/update-components-threat-details-modal-flow diff --git a/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow b/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow new file mode 100644 index 0000000000000..ccbcd7068d542 --- /dev/null +++ b/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds utility for retrieving a detailed action description From 6c19fcd8ce7648e952d0bc28ff73f9a54ee8477b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 15:13:20 -0800 Subject: [PATCH 036/290] Add actionToConfirm flag for rendering conditional content based on action selected --- .../threat-modal/credentials-gate.tsx | 2 +- .../components/threat-modal/index.tsx | 9 +- .../threat-modal/stories/index.stories.tsx | 12 ++- .../threat-modal/threat-actions.tsx | 52 ++++++------ .../threat-modal/threat-details-actions.tsx | 82 +++++++++++++++++++ .../threat-modal/threat-details-gate.tsx | 46 +++++------ .../threat-modal/threat-fix-confirmation.tsx | 1 + .../threat-modal/threat-fix-details.tsx | 1 + .../threat-modal/user-connection-gate.tsx | 2 +- projects/js-packages/scan/src/utils/index.ts | 20 ++--- 10 files changed, 159 insertions(+), 68 deletions(-) create mode 100644 projects/js-packages/components/components/threat-modal/threat-details-actions.tsx diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx index 893d3b2db8760..e134007f99912 100644 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -65,7 +65,7 @@ const CredentialsGate = ( {
{ ! showThreatDetails && ( ) } - ) } +
- { threat.status === 'ignored' && ( - ) } { threat.status === 'current' && ( <> - - { threat.fixable && ( + { actionToConfirm === 'ignore' && ( + + ) } + { threat.fixable && actionToConfirm === 'fix' && ( ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx new file mode 100644 index 0000000000000..e87a17d320a0e --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx @@ -0,0 +1,82 @@ +import { Button } from '@automattic/jetpack-components'; +import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import React, { useCallback, useMemo, useContext } from 'react'; +import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; + +/** + * ThreatActions component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing action details. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer is stale. + * + * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. + */ +const ThreatDetailsActions = ( { + threat, + fixerState, +}: { + threat: Threat; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ): JSX.Element => { + const { onHideThreatDetailsClick } = useContext( ThreatModalContext ); + + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); + + const onHideThreatDetails = useCallback( + ( action: string ) => { + return () => { + onHideThreatDetailsClick( action ); + }; + }, + [ onHideThreatDetailsClick ] + ); + + if ( ! threat.status ) { + return null; + } + + return ( +
+
+ { threat.status === 'ignored' && ( + + ) } + { threat.status === 'current' && ( + <> + + { threat.fixable && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx index 0e5a4a7ebfeb9..2eeb955e0902e 100644 --- a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx @@ -1,6 +1,6 @@ import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactNode, useContext } from 'react'; -import ThreatActions from './threat-actions'; +import React, { ReactElement, useContext } from 'react'; +import ThreatDetailsActions from './threat-details-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; @@ -10,14 +10,14 @@ import { ThreatModalContext } from '.'; /** * ThreatDetailsGate component * - * @param {object} props - The component props. - * @param {Threat} props.threat - The threat object containing details. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {ReactNode} props.children - The child components to render if details are not shown. + * @param {object} props - The component props. + * @param {Threat} props.threat - The threat object containing details. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer status is stale. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. + * @param {ReactElement} props.children - The child components to render if details are not shown. * * @return {JSX.Element} The rendered ThreatDetailsGate component. */ @@ -30,23 +30,23 @@ const ThreatDetailsGate = ( { threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleUpgradeClick: () => void; - children: ReactNode; -} ) => { + children: ReactElement; +} ): JSX.Element => { const { showThreatDetails } = useContext( ThreatModalContext ); - if ( showThreatDetails ) { - return ( - <> - - - - - - - ); + if ( ! showThreatDetails ) { + return children; } - return <>{ children }; + return ( + <> + + + + + + + ); }; export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index c50b9e44231eb..26b61835dcf87 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -47,6 +47,7 @@ const ThreatFixConfirmation = ( { + { /* TODO: Add confirmation message for ignoring/unignoring threats */ } { ! showThreatDetails && ( ) } + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); +}; + +export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx deleted file mode 100644 index e134007f99912..0000000000000 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import React, { ReactElement, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * CredentialsGate component - * - * @param {object} props - The component props. - * @param {boolean} props.siteCredentialsNeeded - Whether the credentials exist. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * @param {ReactElement} props.children - The child components to render if credentials are set. - * - * @return {JSX.Element} The rendered CredentialsGate component. - */ -const CredentialsGate = ( { - siteCredentialsNeeded, - credentialsIsFetching, - credentialsRedirectUrl, - children, -}: { - siteCredentialsNeeded: boolean; - credentialsIsFetching: boolean; - credentialsRedirectUrl: string; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails, onShowThreatDetailsClick } = useContext( ThreatModalContext ); - - if ( ! siteCredentialsNeeded ) { - return children; - } - - return ( - <> - - { __( - 'Before Jetpack can auto-fix threats on your site, it needs your server credentials.', - 'jetpack' - ) } - - } - /> - - - { __( - 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.', - 'jetpack' - ) } - - - - { __( - 'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.', - 'jetpack' - ) } - - -
- { ! showThreatDetails && ( - - ) } - -
- - ); -}; - -export default CredentialsGate; diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 4b546d8329350..9eb6d7f738f13 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,18 +1,14 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import { useMemo, createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; -import ThreatDetailsGate from './threat-details-gate'; import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; actionToConfirm: string | null; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onHideThreatDetailsClick: ( action: string ) => void; + setActionToConfirm: ( action: string ) => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -34,9 +30,8 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * @param {string} props.actionToConfirm - The action to confirm. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Function} props.onShowThreatDetailsClick - The onShowThreatDetailsClick function. - * @param {Function} props.onHideThreatDetailsClick - The onHideThreatDetailsClick function. + * @param {Function} props.setActionToConfirm - The setActionToConfirm function. + * * @return {JSX.Element} The threat modal. */ export default function ThreatModal( { @@ -53,9 +48,7 @@ export default function ThreatModal( { handleIgnoreThreatClick, handleUnignoreThreatClick, actionToConfirm, - showThreatDetails, - onShowThreatDetailsClick, - onHideThreatDetailsClick, + setActionToConfirm, ...modalProps }: { threat: Threat; @@ -71,9 +64,7 @@ export default function ThreatModal( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; actionToConfirm: string | null; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onHideThreatDetailsClick: ( action: string ) => void; + setActionToConfirm: ( action: string ) => void; } & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; @@ -82,64 +73,39 @@ export default function ThreatModal( { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); - const getModalTitle = useMemo( () => { - if ( userConnectionNeeded && ! showThreatDetails ) { - return { __( 'User connection needed', 'jetpack' ) }; - } - - if ( siteCredentialsNeeded && ! showThreatDetails ) { - return { __( 'Site credentials needed', 'jetpack' ) }; - } - - return ( - <> - { threat.title } - { !! threat.severity && } - - ); - }, [ - userConnectionNeeded, - siteCredentialsNeeded, - showThreatDetails, - threat.title, - threat.severity, - ] ); - return ( { getModalTitle }
} + title={ +
+ { threat.title } + { !! threat.severity && } +
+ } size="large" { ...modalProps } >
- - - + userConnectionNeeded={ userConnectionNeeded } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + siteCredentialsNeeded={ siteCredentialsNeeded } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + />
diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 4c8cddcc2b5c8..55b76371f1004 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -12,16 +12,7 @@ const Base = args => { const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); - const [ actionToConfirm, setActionToConfirm ] = useState( null ); - const [ showThreatDetails, setShowThreatDetails ] = useState( true ); - const onShowThreatDetails = useCallback( () => { - setShowThreatDetails( true ); - setActionToConfirm( null ); - }, [] ); - const onHideThreatDetails = useCallback( ( action: string ) => { - setShowThreatDetails( false ); - setActionToConfirm( action ); - }, [] ); + const [ actionToConfirm, setActionToConfirm ] = useState( 'all' ); return (
@@ -31,9 +22,7 @@ const Base = args => { { ...args } onRequestClose={ onRequestClose } actionToConfirm={ actionToConfirm } - showThreatDetails={ showThreatDetails } - onShowThreatDetailsClick={ onShowThreatDetails } - onHideThreatDetailsClick={ onHideThreatDetails } + setActionToConfirm={ setActionToConfirm } /> ) : null }
@@ -70,6 +59,38 @@ ThreatResult.args = { handleUnignoreThreatClick: () => {}, }; +export const AdditionalConnectionsNeeded = Base.bind( {} ); +AdditionalConnectionsNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const UserConnectionNeeded = Base.bind( {} ); UserConnectionNeeded.args = { threat: { @@ -95,6 +116,8 @@ UserConnectionNeeded.args = { isUserConnected: false, hasConnectedOwner: false, handleConnectUser: () => {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -126,7 +149,7 @@ CredentialsNeeded.args = { hasConnectedOwner: true, credentials: false, credentialsIsFetching: false, - credentialsRedirectUrl: '', + credentialsRedirectUrl: '#', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 16fc8c4ee780a..bd20ce0a775c2 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -8,6 +8,22 @@ display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px + + &__title { + display: flex; + justify-content: flex-start; + align-items: center; + } + + &__closed { + max-height: 0; + overflow: hidden; + } + + &__open { + max-height: fit-content; + overflow: hidden; + } } .title { @@ -24,13 +40,25 @@ .modal-actions { display: flex; - justify-content: space-between; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); +} - .threat-actions { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px; - margin-left: auto; +.notice__title { + display: flex; + + svg { + margin-right: calc( var( --spacing-base ) / 2 ); // 4px + } + + p { + font-weight: bold; } +} + +.notice__actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; } \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index a1298eb73cf75..9a6892a784371 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; -import { Threat } from '@automattic/jetpack-scan'; +import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -10,6 +10,7 @@ import { ThreatModalContext } from '.'; * * @param {object} props - The component props. * @param {object} props.threat - The threat object containing action details. + * @param {boolean} props.disabled - Whether the actions are disabled. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. @@ -22,19 +23,22 @@ import { ThreatModalContext } from '.'; */ const ThreatActions = ( { threat, + disabled, fixerState, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, }: { threat: Threat; + disabled?: boolean; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, actionToConfirm, onShowThreatDetailsClick } = - useContext( ThreatModalContext ); + const { closeModal } = useContext( ThreatModalContext ); + + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { handleFixThreatClick?.( [ threat ] ); @@ -57,41 +61,39 @@ const ThreatActions = ( { return (
- -
- { threat.status === 'ignored' && actionToConfirm === 'un-ignore' && ( - + ) } + { threat.status === 'current' && ( + <> + - ) } - { threat.status === 'current' && ( - <> - { actionToConfirm === 'ignore' && ( - - ) } - { threat.fixable && actionToConfirm === 'fix' && ( - - ) } - - ) } -
+ { threat.fixable && ( + + ) } + + ) }
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx deleted file mode 100644 index e87a17d320a0e..0000000000000 --- a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * ThreatActions component - * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing action details. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer is stale. - * - * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. - */ -const ThreatDetailsActions = ( { - threat, - fixerState, -}: { - threat: Threat; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ): JSX.Element => { - const { onHideThreatDetailsClick } = useContext( ThreatModalContext ); - - const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); - - const onHideThreatDetails = useCallback( - ( action: string ) => { - return () => { - onHideThreatDetailsClick( action ); - }; - }, - [ onHideThreatDetailsClick ] - ); - - if ( ! threat.status ) { - return null; - } - - return ( -
-
- { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> - - { threat.fixable && ( - - ) } - - ) } -
-
- ); -}; - -export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx deleted file mode 100644 index 2eeb955e0902e..0000000000000 --- a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactElement, useContext } from 'react'; -import ThreatDetailsActions from './threat-details-actions'; -import ThreatFixDetails from './threat-fix-details'; -import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; -import { ThreatModalContext } from '.'; - -/** - * ThreatDetailsGate component - * - * @param {object} props - The component props. - * @param {Threat} props.threat - The threat object containing details. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {ReactElement} props.children - The child components to render if details are not shown. - * - * @return {JSX.Element} The rendered ThreatDetailsGate component. - */ -const ThreatDetailsGate = ( { - threat, - fixerState, - handleUpgradeClick, - children, -}: { - threat: Threat; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleUpgradeClick: () => void; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails } = useContext( ThreatModalContext ); - - if ( ! showThreatDetails ) { - return children; - } - - return ( - <> - - - - - - - ); -}; - -export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index 26b61835dcf87..a3073537a0762 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,14 +1,16 @@ import { type Threat } from '@automattic/jetpack-scan'; -import CredentialsGate from './credentials-gate'; +import { __ } from '@wordpress/i18n'; +import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; -import UserConnectionGate from './user-connection-gate'; +import ThreatTechnicalDetails from './threat-technical-details'; const ThreatFixConfirmation = ( { threat, fixerState, + handleUpgradeClick, userConnectionNeeded, userIsConnecting, handleConnectUser, @@ -21,6 +23,7 @@ const ThreatFixConfirmation = ( { }: { threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; + handleUpgradeClick: () => void; userConnectionNeeded: boolean; userIsConnecting: boolean; handleConnectUser: () => void; @@ -32,32 +35,55 @@ const ThreatFixConfirmation = ( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ) => { return ( - - - <> - { /* TODO: Determine what we want to display here */ } - - - - { /* TODO: Add confirmation message for ignoring/unignoring threats */ } - - - - + <> + + + + + { siteCredentialsNeeded && userConnectionNeeded && ( + + ) } + { ! siteCredentialsNeeded && userConnectionNeeded && ( + + ) } + { siteCredentialsNeeded && ! userConnectionNeeded && ( + + ) } + + ); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 38793f704886d..bb52a38042c93 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,8 +1,10 @@ +import { Text, Button } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; +import { chevronDown, chevronUp } from '@wordpress/icons'; +import { useState, useCallback } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; -import Text from '../text'; import styles from './styles.module.scss'; /** @@ -14,21 +16,38 @@ import styles from './styles.module.scss'; * @return {JSX.Element | null} The rendered technical details or null if no details are available. */ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { + const [ open, setOpen ] = useState( false ); + + const toggleOpen = useCallback( () => { + setOpen( ! open ); + }, [ open ] ); + if ( ! threat.filename && ! threat.context && ! threat.diff ) { return null; } return (
- { __( 'The technical details', 'jetpack' ) } - { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } +
+ { __( 'The technical details', 'jetpack' ) } +
+
+ { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx deleted file mode 100644 index 1bc2079aa61ec..0000000000000 --- a/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import React, { ReactElement, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * UserConnectionGate component - * - * @param {object} props - The component props. - * @param {boolean} props.userConnectionNeeded - Whether the current user is connected or the site has a connected owner. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {ReactElement} props.children - The child components to render if the user is connected. - * - * @return {JSX.Element} The rendered UserConnectionGate component. - */ -const UserConnectionGate = ( { - userConnectionNeeded, - userIsConnecting, - handleConnectUser, - children, -}: { - userConnectionNeeded: boolean; - userIsConnecting: boolean; - handleConnectUser: () => void; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails, onShowThreatDetailsClick } = useContext( ThreatModalContext ); - - if ( ! userConnectionNeeded ) { - return children; - } - - return ( - <> - - { __( - 'Before Jetpack can ignore and auto-fix threats on your site, a user connection is needed.', - 'jetpack' - ) } - - } - /> - - - { __( - 'A user connection provides Jetpack the access necessary to perform these tasks.', - 'jetpack' - ) } - - - - { __( - 'Once you’ve secured a user connection, all Jetpack features will be available for use.', - 'jetpack' - ) } - - -
- { ! showThreatDetails && ( - - ) } - -
- - ); -}; - -export default UserConnectionGate; From c084a5f1e0293f8bea46623932827766960ca697 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 12:08:52 -0800 Subject: [PATCH 038/290] Move notices to ThreatActions --- .../components/components/threat-modal/threat-actions.tsx | 2 ++ .../components/threat-modal/threat-fix-confirmation.tsx | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 9a6892a784371..cbbc9915d3e14 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -3,6 +3,7 @@ import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,6 +62,7 @@ const ThreatActions = ( { return (
+ { threat.status === 'ignored' && ( - ) } - { credentialsRedirectUrl && ( - - ) } -
-
- } - /> - ); -}; - -export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx new file mode 100644 index 0000000000000..fa4dd5e4f87dc --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx @@ -0,0 +1,46 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; + +/** + * FixerNotice component + * + * @param {object} props - The component props. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer is stale. + * + * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. + */ +const FixerNotice = ( { + fixerState, +}: { + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + let status: 'error' | 'success' | undefined; + let title: string | undefined; + let content: string | undefined; + + if ( fixerState.error ) { + status = 'error'; + title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); + content = __( 'Please try again later or contact support.', 'jetpack' ); + } else if ( fixerState.stale ) { + status = 'error'; + title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); + content = __( 'Please try again later or contact support.', 'jetpack' ); + } else if ( fixerState.inProgress ) { + status = 'success'; + title = __( 'An auto-fixer is in progress', 'jetpack' ); + content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); + } + + return title ? ( +
+ +
+ ) : null; +}; + +export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index bd20ce0a775c2..138f112bb97ad 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -38,26 +38,39 @@ overflow-x: auto; } -.modal-actions { - display: flex; - justify-content: flex-end; - gap: calc( var( --spacing-base ) * 2 ); // 16px; +.modal-footer { padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} + +.fixer-notice { + padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } .notice__title { display: flex; - - svg { - margin-right: calc( var( --spacing-base ) / 2 ); // 4px - } + gap: calc( var( --spacing-base ) / 2 ); // 4px p { font-weight: bold; } } +svg.spinner { + color: var( --jp-black ); + height: 20px; + width: 20px; + margin-left: calc( var( --spacing-base ) / 2 ); // 4px; + margin-right: 6px; + +} + .notice__actions { display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index cbbc9915d3e14..9eb794f6ecc3b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; +import FixerNotice from './fixer-notice'; import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,41 +61,43 @@ const ThreatActions = ( { } return ( -
- - { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> +
+ +
+ { threat.status === 'ignored' && ( - { threat.fixable && ( + ) } + { threat.status === 'current' && ( + <> - ) } - - ) } + { threat.fixable && ( + + ) } + + ) } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index bb52cfb174663..b7e8d6a7eaf74 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,8 +1,8 @@ import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; @@ -39,7 +39,7 @@ const ThreatFixConfirmation = ( { { siteCredentialsNeeded && userConnectionNeeded && ( - ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( - ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( - { - if ( fixerState.error ) { - return ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ); - } - if ( fixerState.stale ) { - return ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ); - } - if ( fixerState.inProgress && ! fixerState.stale ) { - return ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ); - } - return null; + status?: 'warning' | 'error' | 'success' | undefined; + title: string; + content: string; + handleConnectUser?: () => void; + userIsConnecting?: boolean; + credentialsRedirectUrl?: string; + credentialsIsFetching?: boolean; +} ): JSX.Element => { + return ( + +
+ { status === 'success' ? ( + + ) : ( + + ) } + + { title } + +
+ { content } +
+ { handleConnectUser && ( + + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); }; -export default ThreatNotices; +export default ThreatNotice; diff --git a/projects/js-packages/components/components/threats-data-views/stories/data.tsx b/projects/js-packages/components/components/threats-data-views/stories/data.tsx new file mode 100644 index 0000000000000..9563a7f594f58 --- /dev/null +++ b/projects/js-packages/components/components/threats-data-views/stories/data.tsx @@ -0,0 +1,126 @@ +export const data = [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << ; Default.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; -UserConnectedRequired.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; +AdditionalConnectionsNeeded.args = { + data: data, filters: [ { field: 'status', @@ -443,134 +194,9 @@ UserConnectedRequired.args = { ), }; -export const CredentialsRequired = args => ; -CredentialsRequired.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; +UserConnectionNeeded.args = { + data: data, filters: [ { field: 'status', @@ -578,9 +204,30 @@ CredentialsRequired.args = { value: [ 'current' ], }, ], + isUserConnected: false, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + +export const CredentialsRequired = args => ; +CredentialsRequired.args = { + data: data, isUserConnected: true, hasConnectedOwner: true, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert credentials: false, + credentialsRedirectUrl: '#', isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert From b33d1d185120d9122d4f4bc4d1aa2b8744ff4c24 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 18:37:36 -0800 Subject: [PATCH 040/290] Add fixer notices --- .../threat-modal/connection-warning.tsx | 76 ------------- .../components/threat-modal/fixer-notice.tsx | 52 +++++++++ .../threat-modal/stories/index.stories.tsx | 96 ++++++++++++++++ .../threat-modal/styles.module.scss | 29 +++-- .../threat-modal/threat-actions.tsx | 56 ++++----- .../threat-modal/threat-fix-confirmation.tsx | 8 +- .../components/threat-modal/threat-notice.tsx | 107 +++++++++++++----- 7 files changed, 280 insertions(+), 144 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-modal/connection-warning.tsx create mode 100644 projects/js-packages/components/components/threat-modal/fixer-notice.tsx diff --git a/projects/js-packages/components/components/threat-modal/connection-warning.tsx b/projects/js-packages/components/components/threat-modal/connection-warning.tsx deleted file mode 100644 index a8ed47560e7de..0000000000000 --- a/projects/js-packages/components/components/threat-modal/connection-warning.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; -import styles from './styles.module.scss'; - -/** - * ConnectionWarning component - * - * @param {object} props - The component props. - * @param {string} props.title - The title of the warning. - * @param {string} props.content - The content of the warning. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * - * @return {JSX.Element} The rendered ConnectionWarning component. - */ -const ConnectionWarning = ( { - title, - content, - handleConnectUser, - userIsConnecting, - credentialsRedirectUrl, - credentialsIsFetching, -}: { - title: string; - content: string; - handleConnectUser?: () => void; - userIsConnecting?: boolean; - credentialsRedirectUrl?: string; - credentialsIsFetching?: boolean; -} ): JSX.Element => { - return ( - -
- - - { title } - -
- { content } -
- { handleConnectUser && ( - - ) } - { credentialsRedirectUrl && ( - - ) } -
-
- } - /> - ); -}; - -export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx new file mode 100644 index 0000000000000..17af4803c779a --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx @@ -0,0 +1,52 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; + +/** + * FixerNotice component + * + * @param {object} props - The component props. + * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). + * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. + * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. + * @param {boolean} props.fixerState.stale - Whether the fixer is stale. + * + * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. + */ +const FixerNotice = ( { + fixerState, +}: { + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + let status: 'error' | 'success' | undefined; + let title: string | undefined; + let content: string | undefined; + + if ( fixerState.error ) { + status = 'error'; + title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); + content = __( + 'Jetpack encountered a filesystem error when attempting to auto-fix this threat. Please try again later or contact support.', + 'jetpack' + ); + } else if ( fixerState.stale ) { + status = 'error'; + title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); + content = __( + 'Jetpack has been attempting to auto-fix this threat for too long, and something may have gone wrong. Please try again later or contact support.', + 'jetpack' + ); + } else if ( fixerState.inProgress ) { + status = 'success'; + title = __( 'An auto-fixer is in progress', 'jetpack' ); + content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); + } + + return title ? ( +
+ +
+ ) : null; +}; + +export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 55b76371f1004..4f49f5316a246 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -155,6 +155,102 @@ CredentialsNeeded.args = { handleUnignoreThreatClick: () => {}, }; +export const InProgressFixer = Base.bind( {} ); +InProgressFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'in_progress' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const ErrorFixer = Base.bind( {} ); +ErrorFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { error: 'error' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const StaleFixer = Base.bind( {} ); +StaleFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const VulnerableExtension = Base.bind( {} ); VulnerableExtension.args = { threat: { diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index bd20ce0a775c2..138f112bb97ad 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -38,26 +38,39 @@ overflow-x: auto; } -.modal-actions { - display: flex; - justify-content: flex-end; - gap: calc( var( --spacing-base ) * 2 ); // 16px; +.modal-footer { padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} + +.fixer-notice { + padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } .notice__title { display: flex; - - svg { - margin-right: calc( var( --spacing-base ) / 2 ); // 4px - } + gap: calc( var( --spacing-base ) / 2 ); // 4px p { font-weight: bold; } } +svg.spinner { + color: var( --jp-black ); + height: 20px; + width: 20px; + margin-left: calc( var( --spacing-base ) / 2 ); // 4px; + margin-right: 6px; + +} + .notice__actions { display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index cbbc9915d3e14..9eb794f6ecc3b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; +import FixerNotice from './fixer-notice'; import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,41 +61,43 @@ const ThreatActions = ( { } return ( -
- - { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> +
+ +
+ { threat.status === 'ignored' && ( - { threat.fixable && ( + ) } + { threat.status === 'current' && ( + <> - ) } - - ) } + { threat.fixable && ( + + ) } + + ) } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index bb52cfb174663..b7e8d6a7eaf74 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,8 +1,8 @@ import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; @@ -39,7 +39,7 @@ const ThreatFixConfirmation = ( { { siteCredentialsNeeded && userConnectionNeeded && ( - ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( - ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( - { - if ( fixerState.error ) { - return ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ); - } - if ( fixerState.stale ) { - return ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ); - } - if ( fixerState.inProgress && ! fixerState.stale ) { - return ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ); - } - return null; + status?: 'warning' | 'error' | 'success' | undefined; + title: string; + content: string; + handleConnectUser?: () => void; + userIsConnecting?: boolean; + credentialsRedirectUrl?: string; + credentialsIsFetching?: boolean; +} ): JSX.Element => { + return ( + +
+ { status === 'success' ? ( + + ) : ( + + ) } + + { title } + +
+ { content } +
+ { handleConnectUser && ( + + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); }; -export default ThreatNotices; +export default ThreatNotice; From 3541623ed617570fa0fbd8fdaa1bc5a75a9e54ee Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 18:55:32 -0800 Subject: [PATCH 041/290] Fix styling --- ...ixer-notice.tsx => fixer-state-notice.tsx} | 8 +++--- .../threat-modal/styles.module.scss | 26 ++++++++++++------- .../threat-modal/threat-actions.tsx | 4 +-- .../components/threat-modal/threat-notice.tsx | 4 ++- 4 files changed, 25 insertions(+), 17 deletions(-) rename projects/js-packages/components/components/threat-modal/{fixer-notice.tsx => fixer-state-notice.tsx} (88%) diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx similarity index 88% rename from projects/js-packages/components/components/threat-modal/fixer-notice.tsx rename to projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 17af4803c779a..4ade6c4f2b829 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -3,7 +3,7 @@ import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; /** - * FixerNotice component + * FixerStateNotice component * * @param {object} props - The component props. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). @@ -13,7 +13,7 @@ import ThreatNotice from './threat-notice'; * * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. */ -const FixerNotice = ( { +const FixerStateNotice = ( { fixerState, }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; @@ -26,7 +26,7 @@ const FixerNotice = ( { status = 'error'; title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); content = __( - 'Jetpack encountered a filesystem error when attempting to auto-fix this threat. Please try again later or contact support.', + 'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.', 'jetpack' ); } else if ( fixerState.stale ) { @@ -49,4 +49,4 @@ const FixerNotice = ( { ) : null; }; -export default FixerNotice; +export default FixerStateNotice; diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 138f112bb97ad..ccefd7984f5c7 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -53,12 +53,23 @@ padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } -.notice__title { - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px +.notice { + &__title { + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + + p { + font-weight: bold; + } + } + + &__actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } - p { - font-weight: bold; + &__action { + margin-top: calc( var( --spacing-base ) * 2 ); // 16px; } } @@ -69,9 +80,4 @@ svg.spinner { margin-left: calc( var( --spacing-base ) / 2 ); // 4px; margin-right: 6px; -} - -.notice__actions { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px; } \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 9eb794f6ecc3b..c7ade35499866 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,7 +2,7 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; -import FixerNotice from './fixer-notice'; +import FixerStateNotice from './fixer-state-notice'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -62,7 +62,7 @@ const ThreatActions = ( { return (
- +
{ threat.status === 'ignored' && (
- { content } + { content }
{ handleConnectUser && (
- { content } + { content }
{ handleConnectUser && (
+ ); +}; + +export default ThreatIgnoreDetails; From 962a992b854503f35152b9691aa9be6c6a637882 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 19:53:39 -0800 Subject: [PATCH 047/290] Add modal fixer confirmation context --- .../components/threat-modal/index.tsx | 5 --- .../threat-modal/threat-actions.tsx | 22 ++++++------ .../threat-modal/threat-fix-confirmation.tsx | 7 ++-- .../components/threats-data-views/index.tsx | 35 ++++++------------- 4 files changed, 28 insertions(+), 41 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 9eb6d7f738f13..9d6acf1787036 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -8,7 +8,6 @@ import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; actionToConfirm: string | null; - setActionToConfirm: ( action: string ) => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -30,7 +29,6 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * @param {string} props.actionToConfirm - The action to confirm. - * @param {Function} props.setActionToConfirm - The setActionToConfirm function. * * @return {JSX.Element} The threat modal. */ @@ -48,7 +46,6 @@ export default function ThreatModal( { handleIgnoreThreatClick, handleUnignoreThreatClick, actionToConfirm, - setActionToConfirm, ...modalProps }: { threat: Threat; @@ -64,7 +61,6 @@ export default function ThreatModal( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; actionToConfirm: string | null; - setActionToConfirm: ( action: string ) => void; } & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; @@ -89,7 +85,6 @@ export default function ThreatModal( { value={ { closeModal: modalProps.onRequestClose, actionToConfirm, - setActionToConfirm, } } > void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal } = useContext( ThreatModalContext ); + const { closeModal, actionToConfirm } = useContext( ThreatModalContext ); const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); @@ -76,15 +76,17 @@ const ThreatActions = ( { ) } { threat.status === 'current' && ( <> - - { threat.fixable && ( + { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( + + ) } + { threat.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( @@ -469,7 +458,7 @@ export default function ThreatsDataViews( { } return ( - + ); }, }, @@ -493,7 +482,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatModal( items[ 0 ], false )(); + showThreatModal( items[ 0 ], 'ignore' )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -512,7 +501,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatModal( items[ 0 ], false )(); + showThreatModal( items[ 0 ], 'unignore' )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -596,9 +585,7 @@ export default function ThreatsDataViews( { handleIgnoreThreatClick={ onIgnoreThreats } handleUnignoreThreatClick={ onUnignoreThreats } onRequestClose={ hideThreatModal } - showThreatDetails={ showThreatDetails } - onShowThreatDetailsClick={ onShowThreatDetails } - onHideThreatDetailsClick={ onHideThreatDetails } + actionToConfirm={ actionToConfirm } /> ) : null } From 5860eca1af37255011c72fd03e4d9080cf96fb4c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:25:16 -0800 Subject: [PATCH 048/290] Reorg --- .../components/threat-modal/fixer-notice.tsx | 46 ------------------- .../threat-modal/fixer-state-notice.tsx | 6 ++- .../threat-modal/threat-actions.tsx | 4 +- .../threat-modal/threat-fix-confirmation.tsx | 19 ++++++-- .../threat-modal/threat-fix-details.tsx | 21 ++------- .../threat-modal/threat-ignore-details.tsx | 7 ++- .../components/threat-modal/threat-notice.tsx | 8 ++++ 7 files changed, 39 insertions(+), 72 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-modal/fixer-notice.tsx diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx deleted file mode 100644 index fa4dd5e4f87dc..0000000000000 --- a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; - -/** - * FixerNotice component - * - * @param {object} props - The component props. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer is stale. - * - * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. - */ -const FixerNotice = ( { - fixerState, -}: { - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ) => { - let status: 'error' | 'success' | undefined; - let title: string | undefined; - let content: string | undefined; - - if ( fixerState.error ) { - status = 'error'; - title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); - content = __( 'Please try again later or contact support.', 'jetpack' ); - } else if ( fixerState.stale ) { - status = 'error'; - title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); - content = __( 'Please try again later or contact support.', 'jetpack' ); - } else if ( fixerState.inProgress ) { - status = 'success'; - title = __( 'An auto-fixer is in progress', 'jetpack' ); - content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); - } - - return title ? ( -
- -
- ) : null; -}; - -export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 4ade6c4f2b829..d48f12f999059 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,3 +1,4 @@ +import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; @@ -6,6 +7,7 @@ import ThreatNotice from './threat-notice'; * FixerStateNotice component * * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing notice details. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. @@ -14,8 +16,10 @@ import ThreatNotice from './threat-notice'; * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. */ const FixerStateNotice = ( { + threat, fixerState, }: { + threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { let status: 'error' | 'success' | undefined; @@ -44,7 +48,7 @@ const FixerStateNotice = ( { return title ? (
- +
) : null; }; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 28010f083c582..bba6b296823e3 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,13 +56,13 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status ) { + if ( ! threat?.status || threat.status === 'fixed' ) { return null; } return (
- +
{ threat.status === 'ignored' && ( - { isOpen ? ( - - ) : null } + { isOpen ? : null }
); }; From 344696a755b632b7bbb9a0d7037099f4e9010d84 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:47:06 -0800 Subject: [PATCH 050/290] Early return on ThreatActions for fixed threats --- .../components/components/threat-modal/threat-actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index c7ade35499866..d77610350adec 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,7 +56,7 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status ) { + if ( ! threat.status || threat.status === 'fixed' ) { return null; } From 242ca8f74c5904b17a769d15c94a4929d38d97ef Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:50:59 -0800 Subject: [PATCH 051/290] Make threat status check optional --- .../components/components/threat-modal/threat-actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index d77610350adec..33fb46fe55f5a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,7 +56,7 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status || threat.status === 'fixed' ) { + if ( ! threat?.status || threat.status === 'fixed' ) { return null; } From c01432f7e90197c3f59b6f966e68358fe1a8ae98 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 21:00:06 -0800 Subject: [PATCH 052/290] Fix stories --- .../components/threat-modal/stories/index.stories.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index bf1eff8fe8f99..e4701970077d6 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -11,10 +11,13 @@ const Base = args => { const [ isOpen, setIsOpen ] = useState( false ); const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + return (
- { isOpen ? : null } + { isOpen ? ( + + ) : null }
); }; From 9093f0a7465fb56280e9793aa5b24e98f03878a4 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 21:11:42 -0800 Subject: [PATCH 053/290] Fix tests --- .../threats-data-views/test/index.test.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx index 2e7dfea35d673..924398bdcf81f 100644 --- a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx +++ b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx @@ -45,9 +45,28 @@ const data = [ }, ]; +const mockProps = { + filters: [], + onChangeSelection: jest.fn(), + handleUpgradeClick: jest.fn(), + onFixThreats: jest.fn(), + onIgnoreThreats: jest.fn(), + onUnignoreThreats: jest.fn(), + isThreatEligibleForFix: jest.fn().mockReturnValue( true ), + isThreatEligibleForIgnore: jest.fn().mockReturnValue( true ), + isThreatEligibleForUnignore: jest.fn().mockReturnValue( true ), + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: jest.fn(), + credentials: [], + credentialsIsFetching: false, + credentialsRedirectUrl: '/redirect-url', +}; + describe( 'ThreatsDataViews', () => { it( 'renders threat data', () => { - render( ); + render( ); expect( screen.getByText( 'Malicious code found in file: index.php' ) ).toBeInTheDocument(); expect( screen.getByText( 'WooCommerce <= 3.2.3 - Authenticated PHP Object Injection' ) From a02bff67189489076f78df8b95e295d6b02f924b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 21 Nov 2024 08:59:24 -0800 Subject: [PATCH 054/290] Remove jest --- .../threats-data-views/test/index.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx index 924398bdcf81f..c9c83afd24f68 100644 --- a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx +++ b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx @@ -47,18 +47,18 @@ const data = [ const mockProps = { filters: [], - onChangeSelection: jest.fn(), - handleUpgradeClick: jest.fn(), - onFixThreats: jest.fn(), - onIgnoreThreats: jest.fn(), - onUnignoreThreats: jest.fn(), - isThreatEligibleForFix: jest.fn().mockReturnValue( true ), - isThreatEligibleForIgnore: jest.fn().mockReturnValue( true ), - isThreatEligibleForUnignore: jest.fn().mockReturnValue( true ), + onChangeSelection: () => {}, + handleUpgradeClick: () => {}, + onFixThreats: () => {}, + onIgnoreThreats: () => {}, + onUnignoreThreats: () => {}, + isThreatEligibleForFix: () => true, + isThreatEligibleForIgnore: () => true, + isThreatEligibleForUnignore: () => true, isUserConnected: true, hasConnectedOwner: true, userIsConnecting: false, - handleConnectUser: jest.fn(), + handleConnectUser: () => {}, credentials: [], credentialsIsFetching: false, credentialsRedirectUrl: '/redirect-url', From 220d28cb0b9495aaee61afe8a15f0c50735fa25e Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 22 Nov 2024 09:39:12 -0700 Subject: [PATCH 055/290] Use custom button for threat details toggle (#40298) --- .../threat-modal/fixer-state-notice.tsx | 55 +++++++++++-------- .../threat-modal/styles.module.scss | 23 +++++--- .../threat-modal/threat-technical-details.tsx | 44 +++++++++------ 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 4ade6c4f2b829..4c8a8a5cb53db 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,4 +1,5 @@ import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; @@ -18,29 +19,39 @@ const FixerStateNotice = ( { }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { - let status: 'error' | 'success' | undefined; - let title: string | undefined; - let content: string | undefined; + const { status, title, content } = useMemo( () => { + if ( fixerState.error ) { + return { + status: 'error' as const, + title: __( 'An error occurred auto-fixing this threat', 'jetpack' ), + content: __( + 'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.', + 'jetpack' + ), + }; + } - if ( fixerState.error ) { - status = 'error'; - title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); - content = __( - 'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.', - 'jetpack' - ); - } else if ( fixerState.stale ) { - status = 'error'; - title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); - content = __( - 'Jetpack has been attempting to auto-fix this threat for too long, and something may have gone wrong. Please try again later or contact support.', - 'jetpack' - ); - } else if ( fixerState.inProgress ) { - status = 'success'; - title = __( 'An auto-fixer is in progress', 'jetpack' ); - content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); - } + if ( fixerState.stale ) { + return { + status: 'error' as const, + title: __( 'The auto-fixer is taking longer than expected', 'jetpack' ), + content: __( + 'Jetpack has been attempting to auto-fix this threat for too long, and something may have gone wrong. Please try again later or contact support.', + 'jetpack' + ), + }; + } + + if ( fixerState.inProgress ) { + return { + status: 'success' as const, + title: __( 'An auto-fixer is in progress', 'jetpack' ), + content: __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ), + }; + } + + return {}; + }, [ fixerState ] ); return title ? (
diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 4850724be6212..874a2285a098d 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -14,15 +14,24 @@ justify-content: flex-start; align-items: center; } +} - &__closed { - max-height: 0; - overflow: hidden; - } +.section__toggle { + border: none; + background: none; + padding: 0; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + align-items: center; + transition: text-underline-offset 0.2s; - &__open { - max-height: fit-content; - overflow: hidden; + &:hover { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: calc( var( --spacing-base ) / 2 ); // 4px } } diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index bb52a38042c93..182bdd863e4b1 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,7 +1,7 @@ -import { Text, Button } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import { chevronDown, chevronUp } from '@wordpress/icons'; +import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; import { useState, useCallback } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; @@ -29,25 +29,35 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = return (
- { __( 'The technical details', 'jetpack' ) } -
-
- { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } + > + + { open + ? __( 'Hide the technical details', 'jetpack' ) + : __( 'Show the technical details', 'jetpack' ) } + + +
+ { open && ( +
+ { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ) }
); }; From 896f7e0ad6b38e6d875543fad4554d9c27d2975f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:00:40 -0800 Subject: [PATCH 056/290] Use Button and override internal styles --- .../threat-modal/styles.module.scss | 23 +++++++------------ .../threat-modal/threat-technical-details.tsx | 21 +++++++++-------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 874a2285a098d..2ecec5722de18 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -9,29 +9,22 @@ flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px - &__title { + &__content { display: flex; - justify-content: flex-start; + gap: calc( var( --spacing-base ) / 2 ); // 4px align-items: center; } } -.section__toggle { - border: none; - background: none; - padding: 0; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px - align-items: center; - transition: text-underline-offset 0.2s; +.section .section__toggle { + text-decoration: none; + + &:focus { + box-shadow: none; + } &:hover { text-decoration: underline; - text-decoration-thickness: 2px; - text-underline-offset: calc( var( --spacing-base ) / 2 ); // 4px } } diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 182bdd863e4b1..ba9e0a76ff9a9 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,4 +1,4 @@ -import { Text } from '@automattic/jetpack-components'; +import { Text, Button } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; @@ -29,19 +29,22 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = return (
- +
+ + { open + ? __( 'Hide the technical details', 'jetpack' ) + : __( 'Show the technical details', 'jetpack' ) } + + +
+
{ open && (
Date: Fri, 22 Nov 2024 09:04:48 -0800 Subject: [PATCH 057/290] Fix classes --- .../components/threat-modal/styles.module.scss | 12 ++++++------ .../threat-modal/threat-technical-details.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 2ecec5722de18..d43fa45066aa9 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -8,12 +8,6 @@ display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px - - &__content { - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px - align-items: center; - } } .section .section__toggle { @@ -26,6 +20,12 @@ &:hover { text-decoration: underline; } + + &_content { + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + align-items: center; + } } .title { diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index ba9e0a76ff9a9..dbb1e120d640a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -36,7 +36,7 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = aria-controls={ `threat-details-${ threat.id }` } onClick={ toggleOpen } > -
+
{ open ? __( 'Hide the technical details', 'jetpack' ) From 94ed99a29d2ae08197593f64b848df43d55e7102 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:07:56 -0800 Subject: [PATCH 058/290] Move fixerState comp to ThreatFixConfirmation --- .../components/components/threat-modal/index.tsx | 9 ++------- .../components/threat-modal/threat-fix-confirmation.tsx | 9 ++++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 7e9297e2e6086..404faf892a169 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,6 +1,6 @@ -import { type Threat, getFixerState } from '@automattic/jetpack-scan'; +import { type Threat } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; -import { useMemo, createContext } from 'react'; +import { createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; @@ -61,10 +61,6 @@ export default function ThreatModal( { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; - const fixerState = useMemo( () => { - return getFixerState( threat.fixer ); - }, [ threat.fixer ] ); - return ( void; userConnectionNeeded: boolean; userIsConnecting: boolean; @@ -33,6 +32,10 @@ const ThreatFixConfirmation = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ) => { + const fixerState = useMemo( () => { + return getFixerState( threat.fixer ); + }, [ threat.fixer ] ); + return ( <> From 33268ee082fcfc3f07a36c2688bd9ed903174351 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:31:33 -0800 Subject: [PATCH 059/290] Rely heavily on context provider --- .../components/threat-modal/index.tsx | 36 +++++++---- .../threat-modal/threat-actions.tsx | 46 +++++--------- .../threat-modal/threat-fix-confirmation.tsx | 63 ++++--------------- .../threat-modal/threat-fix-details.tsx | 19 ++---- .../components/threat-modal/threat-notice.tsx | 35 +++++------ .../threat-modal/threat-summary.tsx | 36 +++++++---- .../threat-modal/threat-technical-details.tsx | 11 ++-- 7 files changed, 104 insertions(+), 142 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 404faf892a169..aa0b1577559af 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -7,6 +7,17 @@ import styles from './styles.module.scss'; import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; + threat: Threat; + handleUpgradeClick?: () => void; + userConnectionNeeded: boolean; + handleConnectUser: () => void; + userIsConnecting: boolean; + siteCredentialsNeeded: boolean; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -76,21 +87,20 @@ export default function ThreatModal( { - +
diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 33fb46fe55f5a..08ab0d641a22b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; -import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { getFixerState, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import FixerStateNotice from './fixer-state-notice'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -9,35 +9,23 @@ import { ThreatModalContext } from '.'; /** * ThreatActions component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing action details. - * @param {boolean} props.disabled - Whether the actions are disabled. - * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). - * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress. - * @param {boolean} props.fixerState.error - Whether the fixer encountered an error. - * @param {boolean} props.fixerState.stale - Whether the fixer is stale. - * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. - * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. - * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. */ -const ThreatActions = ( { - threat, - disabled, - fixerState, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, -}: { - threat: Threat; - disabled?: boolean; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; -} ): JSX.Element => { - const { closeModal } = useContext( ThreatModalContext ); +const ThreatActions = (): JSX.Element => { + const { + closeModal, + threat, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + userConnectionNeeded, + siteCredentialsNeeded, + } = useContext( ThreatModalContext ); + const disabled = userConnectionNeeded || siteCredentialsNeeded; + + const fixerState = useMemo( () => { + return getFixerState( threat.fixer ); + }, [ threat.fixer ] ); const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index ca3bdc37956d6..97d3e4a3c968a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,46 +1,24 @@ -import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; +import { useContext } from 'react'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; +import { ThreatModalContext } from '.'; -const ThreatFixConfirmation = ( { - threat, - handleUpgradeClick, - userConnectionNeeded, - userIsConnecting, - handleConnectUser, - siteCredentialsNeeded, - credentialsIsFetching, - credentialsRedirectUrl, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, -}: { - threat: Threat; - handleUpgradeClick: () => void; - userConnectionNeeded: boolean; - userIsConnecting: boolean; - handleConnectUser: () => void; - siteCredentialsNeeded: boolean; - credentialsIsFetching: boolean; - credentialsRedirectUrl: string; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; -} ) => { - const fixerState = useMemo( () => { - return getFixerState( threat.fixer ); - }, [ threat.fixer ] ); - +/** + * ThreatFixConfirmation component + * + * @return {JSX.Element} The rendered fix confirmation. + */ +const ThreatFixConfirmation = () => { + const { userConnectionNeeded, siteCredentialsNeeded } = useContext( ThreatModalContext ); return ( <> - - - + + + { siteCredentialsNeeded && userConnectionNeeded && ( ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( @@ -61,8 +35,6 @@ const ThreatFixConfirmation = ( { 'A user connection provides Jetpack the access necessary to ignore and auto-fix threats on your site.', 'jetpack' ) } - handleConnectUser={ handleConnectUser } - userIsConnecting={ userIsConnecting } /> ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( @@ -72,18 +44,9 @@ const ThreatFixConfirmation = ( { 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to ignore and auto-fix threats detected on your site.', 'jetpack' ) } - credentialsIsFetching={ credentialsIsFetching } - credentialsRedirectUrl={ credentialsRedirectUrl } /> ) } - + ); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx index 72e2dbc3c1b2f..618ca87506309 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx @@ -1,26 +1,19 @@ -import { Threat, getFixerDescription } from '@automattic/jetpack-scan'; +import { getFixerDescription } from '@automattic/jetpack-scan'; import { __, sprintf } from '@wordpress/i18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useContext } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; import Text from '../text'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatFixDetails component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing fix details. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade click events. - * * @return {JSX.Element | null} The rendered fix details or null if no fixable details are available. */ -const ThreatFixDetails = ( { - threat, - handleUpgradeClick, -}: { - threat: Threat; - handleUpgradeClick?: () => void; -} ): JSX.Element => { +const ThreatFixDetails = (): JSX.Element => { + const { threat, handleUpgradeClick } = useContext( ThreatModalContext ); + const title = useMemo( () => { if ( threat.status === 'fixed' ) { return __( 'How did Jetpack fix it?', 'jetpack' ); diff --git a/projects/js-packages/components/components/threat-modal/threat-notice.tsx b/projects/js-packages/components/components/threat-modal/threat-notice.tsx index fc4cdba5a6ccf..a0451e89f56b2 100644 --- a/projects/js-packages/components/components/threat-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-notice.tsx @@ -2,19 +2,17 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; +import { useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatNotice component * - * @param {object} props - The component props. - * @param {string} props.status - The status of the notice. - * @param {string} props.title - The title of the notice. - * @param {string} props.content - The content of the notice. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. + * @param {object} props - The component props. + * @param {string} props.status - The status of the notice. + * @param {string} props.title - The title of the notice. + * @param {string} props.content - The content of the notice. * * @return {JSX.Element} The rendered ThreatNotice component. */ @@ -22,19 +20,20 @@ const ThreatNotice = ( { status = 'warning', title, content, - handleConnectUser, - userIsConnecting, - credentialsRedirectUrl, - credentialsIsFetching, }: { status?: 'warning' | 'error' | 'success' | undefined; title: string; content: string; - handleConnectUser?: () => void; - userIsConnecting?: boolean; - credentialsRedirectUrl?: string; - credentialsIsFetching?: boolean; } ): JSX.Element => { + const { + userConnectionNeeded, + userIsConnecting, + handleConnectUser, + siteCredentialsNeeded, + credentialsRedirectUrl, + credentialsIsFetching, + } = useContext( ThreatModalContext ); + return ( { content }
- { handleConnectUser && ( + { userConnectionNeeded && ( -
- ) } -
-); +/** + * ThreatSummary component + * + * @return {JSX.Element} The rendered threat summary. + */ +const ThreatSummary = (): JSX.Element => { + const { threat } = useContext( ThreatModalContext ); + + return ( +
+ { !! threat.description && { threat.description } } + { !! threat.source && ( +
+ +
+ ) } +
+ ); +}; export default ThreatSummary; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index dbb1e120d640a..92720ef715bb8 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,21 +1,20 @@ import { Text, Button } from '@automattic/jetpack-components'; -import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useContext } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatTechnicalDetails component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing technical details. - * * @return {JSX.Element | null} The rendered technical details or null if no details are available. */ -const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { +const ThreatTechnicalDetails = (): JSX.Element => { + const { threat } = useContext( ThreatModalContext ); + const [ open, setOpen ] = useState( false ); const toggleOpen = useCallback( () => { From 1e2333e486bd5c475a11d47f8cf819ee08de0ff2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 09:37:20 -0800 Subject: [PATCH 060/290] Fix styles --- .../components/components/threat-modal/styles.module.scss | 6 +----- .../components/threat-modal/threat-technical-details.tsx | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index d43fa45066aa9..1720d59b1f90c 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -13,15 +13,11 @@ .section .section__toggle { text-decoration: none; - &:focus { - box-shadow: none; - } - &:hover { text-decoration: underline; } - &_content { + &__content { display: flex; gap: calc( var( --spacing-base ) / 2 ); // 4px align-items: center; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 92720ef715bb8..f7b14581f786f 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -35,7 +35,7 @@ const ThreatTechnicalDetails = (): JSX.Element => { aria-controls={ `threat-details-${ threat.id }` } onClick={ toggleOpen } > -
+
{ open ? __( 'Hide the technical details', 'jetpack' ) From 538694fc62b6833277b41f40960505cdb52d6e20 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 10:18:40 -0800 Subject: [PATCH 061/290] Add support link --- .../threat-modal/fixer-state-notice.tsx | 34 +++++++++++++++---- .../components/threat-modal/threat-notice.tsx | 12 +++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 8d08e4914fca7..fa755769b10b6 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,3 +1,6 @@ +import { Button } from '@automattic/jetpack-components'; +import { CONTACT_SUPPORT_URL } from '@automattic/jetpack-scan'; +import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useMemo } from 'react'; import styles from './styles.module.scss'; @@ -19,14 +22,28 @@ const FixerStateNotice = ( { }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { + const getInterpolatedContent = (): JSX.Element => { + return createInterpolateElement( + __( 'Please try again or contact support.', 'jetpack' ), + { + supportLink: +
+ +
), }, { diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index 820b07916687b..f260b33bbf48a 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -1,15 +1,18 @@ @import '@wordpress/dataviews/build-style/style.css'; .threat__title { - color: var( --jp-gray-80 ); - font-weight: 510; - white-space: initial; + min-height: 24px; + max-width: fit-content; + display: flex; + align-items: center; + + .threat__title__link { + text-decoration: none; + } } .threat__description { - color: var( --jp-gray-80 ); font-size: 12px; - white-space: initial; } .threat__fixedOn, From 1716a45c38b3f56d84e78cc588435976ae8f6d4a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 13:57:34 -0800 Subject: [PATCH 065/290] Add slight padding to overflow issues --- .../components/components/threats-data-views/styles.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index f260b33bbf48a..bb992ea48cdac 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -5,6 +5,7 @@ max-width: fit-content; display: flex; align-items: center; + padding-left: calc( var( --spacing-base ) / 4 ); // 2px .threat__title__link { text-decoration: none; From aa0f8e571fc4591a44dbafce06d1543ac6ae9535 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 14:03:06 -0800 Subject: [PATCH 066/290] Add global override, over awkward title offset --- .../components/threats-data-views/styles.module.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index bb992ea48cdac..ac9cf71fb5008 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -1,11 +1,16 @@ @import '@wordpress/dataviews/build-style/style.css'; +:global { + .dataviews-view-list .dataviews-view-list__primary-field { + overflow: visible; + } +} + .threat__title { min-height: 24px; max-width: fit-content; display: flex; align-items: center; - padding-left: calc( var( --spacing-base ) / 4 ); // 2px .threat__title__link { text-decoration: none; From fbd709181d952ac88370b16df12e9ed7bc010644 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 14:08:17 -0800 Subject: [PATCH 067/290] Remove unneeded styles while global override is in place --- .../components/threats-data-views/styles.module.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index ac9cf71fb5008..2b2e971d0b5c4 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -7,10 +7,7 @@ } .threat__title { - min-height: 24px; max-width: fit-content; - display: flex; - align-items: center; .threat__title__link { text-decoration: none; From a51c9069a5dfb65758951240484d459f5c2b0529 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 3 Dec 2024 11:13:24 -0800 Subject: [PATCH 068/290] Fix text domain --- .../components/threat-modal/threat-ignore-details.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx b/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx index cb7d5ecc52261..1197f8578ece4 100644 --- a/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx @@ -1,7 +1,7 @@ -import { Text, Button, getRedirectUrl } from '@automattic/jetpack-components'; import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useContext } from 'react'; +import { Text, Button, getRedirectUrl } from '@automattic/jetpack-components'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -17,14 +17,14 @@ const ThreatIgnoreDetails = () => { return (
- { __( 'Do you really want to ignore this threat?', 'jetpack' ) } + { __( 'Do you really want to ignore this threat?', 'jetpack-components' ) } { /* TODO: Ensure we only direct supported site to Codeable */ } { createInterpolateElement( __( 'By choosing to ignore this threat, you acknowledge that you have reviewed the detected code. You are accepting the risks of maintaining a potentially malicious or vulnerable file on your site. If you are unsure, please request an estimate with Codeable.', - 'jetpack' + 'jetpack-components' ), { codeableLink: + +
+ ) } +
+ } > { notice && } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index e70d2cdb076c7..adf7dc594b907 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From a240e20764dc17cdeb2e5a44cf5dae274a9fea8a Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 071/290] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 9c8f30b7b8067..4aa517f5f120b 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import AdminSectionHero from '../../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } + { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } -
- -
} /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..1c5cc6cac49b9 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; +import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useFixers from '../../hooks/use-fixers'; +import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return list.filter( threat => { + const threatId = parseInt( threat.id ); + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
- -
} /> diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From b9bfed3145a9255de5fb1398a6a624bd0fd34cea Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 072/290] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
- diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adf7dc594b907..adc0cee561ba5 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From f02081f15c804f4f4794054db29b23e9bc0eed24 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 29 Nov 2024 21:00:37 -0700 Subject: [PATCH 073/290] Protect: add ShieldIcon component --- .../components/admin-section-hero/index.tsx | 21 ++- .../stories/index.stories.jsx | 4 +- .../src/js/components/shield-icon/index.tsx | 165 ++++++++++++++++++ .../shield-icon/stories/index.stories.tsx | 50 ++++++ 4 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ed83bebc8638..758c8c21e0193 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,9 +1,6 @@ -import { - AdminSectionHero as JetpackAdminSectionHero, - H3, - getIconBySlug, -} from '@automattic/jetpack-components'; +import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; +import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -15,7 +12,7 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean } >; + Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -44,17 +41,23 @@ const AdminSectionHero: AdminSectionHeroComponent = ( { AdminSectionHero.Heading = ( { children, + variant = 'default', showIcon = false, }: { children: React.ReactNode; + variant?: 'default' | 'success' | 'error'; showIcon?: boolean; } ) => { - const Icon = getIconBySlug( 'protect' ); - return (

{ children } - { showIcon && } + { showIcon && ( + + ) }

); }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index 7d5b4f8066c93..ca2dfda7fc98e 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -12,7 +12,9 @@ Default.args = { main: ( <> - { 'No threats found' } + + { 'No threats found' } + { 'Most recent results' } diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..3bf7f479f0051 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/index.tsx @@ -0,0 +1,165 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.variant - Icon variant. + * @param {string} props.fill - Icon fill color. + * @param {string} props.className - Additional class names. + * @param {number} props.height - Icon height. + * @return {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ShieldIcon( { + variant = 'default', + height = 32, + className, + fill, +}: { + variant: + | 'default' + | 'success' + | 'error' + | 'default-outline' + | 'success-outline' + | 'error-outline'; + className?: string; + height?: number; + fill?: string; +} ): JSX.Element { + if ( 'error-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'error' === variant ) { + return ( + + + + + ); + } + + if ( 'success-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'success' === variant ) { + return ( + + + + + ); + } + + if ( 'default-outline' === variant ) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..d10365f4b0834 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ShieldIcon from '../index'; + +export default { + title: 'Plugins/Protect/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ + 'default', + 'success', + 'error', + 'default-outline', + 'success-outline', + 'error-outline', + ], + }, + fill: { + control: 'color', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'default', +}; + +export const SuccessVariant = args => ; +SuccessVariant.args = { + variant: 'success', +}; + +export const ErrorVariant = args => ; +ErrorVariant.args = { + variant: 'error', +}; From 6c73d5aa12eda7d1e1812f416d1f91e6cb9274f3 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 4 Dec 2024 20:26:54 -0700 Subject: [PATCH 074/290] Protect: Add ShieldIcon Component (#40402) --- .../components/changelog/add-shield-icon | 4 + .../components/shield-icon/index.tsx | 79 +++++++++ .../shield-icon/stories/index.stories.tsx | 54 ++++++ projects/js-packages/components/index.ts | 1 + .../protect/changelog/refactor-alert-icon | 5 + .../components/admin-section-hero/index.tsx | 20 ++- .../admin-section-hero/styles.module.scss | 4 +- .../src/js/components/alert-icon/index.jsx | 74 -------- .../alert-icon/stories/index.stories.jsx | 18 -- .../components/alert-icon/styles.module.scss | 11 -- .../src/js/components/shield-icon/index.tsx | 165 ------------------ .../shield-icon/stories/index.stories.tsx | 50 ------ .../history/history-admin-section-hero.tsx | 2 +- .../routes/scan/scan-admin-section-hero.tsx | 2 +- 14 files changed, 162 insertions(+), 327 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-shield-icon create mode 100644 projects/js-packages/components/components/shield-icon/index.tsx create mode 100644 projects/js-packages/components/components/shield-icon/stories/index.stories.tsx create mode 100644 projects/plugins/protect/changelog/refactor-alert-icon delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/js-packages/components/changelog/add-shield-icon b/projects/js-packages/components/changelog/add-shield-icon new file mode 100644 index 0000000000000..5c6cc27eeb809 --- /dev/null +++ b/projects/js-packages/components/changelog/add-shield-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add ShieldIcon component diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..fee9f4d70c463 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +const COLORS = { + error: '#D63638', + warning: '#F0B849', + success: '#069E08', + default: '#1d2327', +}; + +/** + * Protect Shield SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.className - Additional class names. + * @param {string} props.contrast - Icon contrast color. Overrides variant. + * @param {string} props.fill - Icon fill color (default, success, warning, error, or a custom color code string). Overrides variant. + * @param {number} props.height - Icon height (px). Width is calculated based on height. + * @param {string} props.icon - Icon variant (success, error). Overrides variant. + * @param {boolean} props.outline - When enabled, the icon will use an outline style. + * @param {string} props.variant - Icon variant (default, success, error). + * + * @return {React.ReactElement} Protect Shield SVG Icon + */ +export default function ShieldIcon( { + className, + contrast = '#fff', + fill, + height = 32, + icon, + outline = false, + variant = 'default', +}: { + className?: string; + contrast?: string; + fill?: 'default' | 'success' | 'warning' | 'error' | string; + height?: number; + icon?: 'success' | 'error'; + outline?: boolean; + variant: 'default' | 'success' | 'warning' | 'error'; +} ): JSX.Element { + const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; + const iconFill = outline ? shieldFill : contrast; + const iconVariant = icon || variant; + + return ( + + + { 'success' === iconVariant && ( + + ) } + { [ 'warning', 'error' ].includes( iconVariant ) && ( + + ) } + + ); +} diff --git a/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..b5a16d4da4075 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,54 @@ +import ShieldIcon from '../index'; + +export default { + title: 'JS Packages/Components/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ 'default', 'success', 'warning', 'error' ], + }, + icon: { + control: { + type: 'select', + }, + options: [ 'success', 'error' ], + }, + fill: { + control: 'color', + }, + outline: { + control: 'boolean', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'success', + outline: false, +}; + +export const Variants = () => { + return ( +
+
+ + + + +
+
+ + + + +
+
+ ); +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index eb90df97ad5fe..4b0f3612012e7 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ShieldIcon } from './components/shield-icon'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/plugins/protect/changelog/refactor-alert-icon b/projects/plugins/protect/changelog/refactor-alert-icon new file mode 100644 index 0000000000000..46b4c247b1b9f --- /dev/null +++ b/projects/plugins/protect/changelog/refactor-alert-icon @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Refactored icon component code. + + diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 758c8c21e0193..5ccf607698084 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,6 +1,9 @@ -import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; +import { + AdminSectionHero as JetpackAdminSectionHero, + H3, + ShieldIcon, +} from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; -import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -12,7 +15,12 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; + Heading: React.FC< { + children: React.ReactNode; + showIcon?: boolean; + variant?: 'default' | 'success' | 'error'; + outline?: boolean; + } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -53,8 +61,10 @@ AdminSectionHero.Heading = ( { { children } { showIcon && ( ) } diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index 5881bcd910045..a414aa9216f5c 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -13,7 +13,7 @@ } .heading-icon { - margin-left: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } @@ -23,4 +23,4 @@ .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px -} \ No newline at end of file +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
- - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx deleted file mode 100644 index 107e513394d42..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable react/react-in-jsx-scope */ -import React from 'react'; -import AlertIcon from '../index.jsx'; - -export default { - title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, -}; - -const FooterTemplate = args => ; -export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx deleted file mode 100644 index 3bf7f479f0051..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @param {object} props - Component props. - * @param {string} props.variant - Icon variant. - * @param {string} props.fill - Icon fill color. - * @param {string} props.className - Additional class names. - * @param {number} props.height - Icon height. - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ShieldIcon( { - variant = 'default', - height = 32, - className, - fill, -}: { - variant: - | 'default' - | 'success' - | 'error' - | 'default-outline' - | 'success-outline' - | 'error-outline'; - className?: string; - height?: number; - fill?: string; -} ): JSX.Element { - if ( 'error-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'error' === variant ) { - return ( - - - - - ); - } - - if ( 'success-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'success' === variant ) { - return ( - - - - - ); - } - - if ( 'default-outline' === variant ) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx deleted file mode 100644 index d10365f4b0834..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ShieldIcon from '../index'; - -export default { - title: 'Plugins/Protect/Sheild Icon', - component: ShieldIcon, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
- -
- ), - ], - argTypes: { - variant: { - control: { - type: 'select', - }, - options: [ - 'default', - 'success', - 'error', - 'default-outline', - 'success-outline', - 'error-outline', - ], - }, - fill: { - control: 'color', - }, - }, -}; - -export const Default = args => ; -Default.args = { - variant: 'default', -}; - -export const SuccessVariant = args => ; -SuccessVariant.args = { - variant: 'success', -}; - -export const ErrorVariant = args => ; -ErrorVariant.args = { - variant: 'error', -}; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 4aa517f5f120b..141c51cde284a 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -60,7 +60,7 @@ const HistoryAdminSectionHero: React.FC = () => { __( 'Most recent results', 'jetpack-protect' ) ) } - + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 1c5cc6cac49b9..9e1b9c102a037 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -95,7 +95,7 @@ const ScanAdminSectionHero: React.FC = () => { anchor={ dailyScansPopoverAnchor } /> ) } - + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ From 1118627d51569f55a638f62341c5aa1da620dbc2 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 5 Dec 2024 10:50:27 -0700 Subject: [PATCH 075/290] Protect: Integrate ThreatsDataViews Component (#40076) --- pnpm-lock.yaml | 3 + .../add-threat-subtitle-and-icon-utils | 4 + projects/js-packages/scan/src/utils/index.ts | 32 +- .../protect/changelog/add-threats-data-views | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 30 +- projects/plugins/protect/src/js/api.ts | 3 +- .../src/js/components/admin-page/index.jsx | 12 +- .../components/admin-page/styles.module.scss | 11 + .../error-admin-section-hero/index.tsx | 4 - .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 14 +- .../src/js/components/navigation/badge.jsx | 101 ------ .../src/js/components/navigation/group.jsx | 51 --- .../src/js/components/navigation/index.jsx | 73 ----- .../src/js/components/navigation/item.jsx | 85 ----- .../src/js/components/navigation/label.jsx | 24 -- .../components/navigation/styles.module.scss | 142 --------- .../navigation/use-menu-navigation.js | 92 ------ .../js/components/paid-accordion/index.jsx | 192 ----------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 202 ------------ .../src/js/components/pricing-table/index.jsx | 4 +- .../components/protect-check-icon/index.tsx | 25 -- .../js/components/scan-navigation/index.jsx | 44 --- .../js/components/threat-fix-header/index.jsx | 7 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 -------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 -------- .../js/components/threats-list/pagination.jsx | 142 --------- .../js/components/threats-list/paid-list.jsx | 253 --------------- .../threats-list/styles.module.scss | 129 -------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/hooks/use-protect-data/index.ts | 173 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../protect/src/js/routes/firewall/index.jsx | 3 +- .../history/history-admin-section-hero.tsx | 84 ----- .../src/js/routes/scan/history/index.jsx | 301 ------------------ .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 37 --- .../protect/src/js/routes/scan/index.jsx | 79 +++-- .../src/js/routes/scan/onboarding-steps.jsx | 61 ++-- .../routes/scan/scan-admin-section-hero.tsx | 138 ++++---- .../src/js/routes/scan/scan-footer.jsx | 143 --------- .../js/routes/scan/scan-results-data-view.tsx | 56 ++++ .../scan/scanning-admin-section-hero.tsx | 25 +- .../src/js/routes/scan/styles.module.scss | 20 +- projects/plugins/protect/webpack.config.js | 18 ++ 53 files changed, 363 insertions(+), 3668 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils create mode 100644 projects/plugins/protect/changelog/add-threats-data-views delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/badge.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/group.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/item.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/label.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/scan-navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/routes/scan/scan-footer.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a6d147b9c7eb..fbb0bbdb03d94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4202,6 +4202,9 @@ importers: specifier: 6.2.2 version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@automattic/babel-plugin-replace-textdomain': + specifier: workspace:* + version: link:../../js-packages/babel-plugin-replace-textdomain '@automattic/jetpack-webpack-config': specifier: workspace:* version: link:../../js-packages/webpack-config diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils new file mode 100644 index 0000000000000..ad8fa81458278 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add utilities for generating threat subtitle and icons diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index fbafc9fcede36..29e017c837b68 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => { return null; }; +export const getThreatIcon = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return 'wordpress-alt'; + case 'plugin': + return 'plugins'; + case 'theme': + return 'appearance'; + case 'file': + return 'media-code'; + default: + return 'shield-alt'; + } +}; + +export const getThreatSubtitle = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); + case 'plugin': + return __( 'Vulnerable Plugin', 'jetpack-scan' ); + case 'theme': + return __( 'Vulnerable Theme', 'jetpack-scan' ); + case 'file': + return __( 'File Threat', 'jetpack-scan' ); + default: + return __( 'Threat', 'jetpack-scan' ); + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); @@ -124,7 +154,7 @@ export const getFixerDescription = ( threat: Threat ) => { } break; case 'update': - if ( threat.fixedIn && threat.extension.name ) { + if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ __( 'Update %1$s to version %2$s', 'jetpack-scan' ), diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views new file mode 100644 index 0000000000000..e15bd6a461a71 --- /dev/null +++ b/projects/plugins/protect/changelog/add-threats-data-views @@ -0,0 +1,5 @@ +Significance: minor +Type: changed + +Added DataViews component for viewing scan results. + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index 9d11e44dc60af..8708e2c101c9f 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -49,6 +49,7 @@ "react-router-dom": "6.2.2" }, "devDependencies": { + "@automattic/babel-plugin-replace-textdomain": "workspace:*", "@automattic/jetpack-webpack-config": "workspace:*", "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index bd034c375caf9..8ea1dec7156e7 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -207,43 +207,19 @@ public static function fetch_from_api() { * Normalize API Data * Formats the payload from the Scan API into an instance of History_Model. * - * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility. - * * @param object $scan_data The data returned by the scan API. * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..97d11fd5c0f2b 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,6 +1,7 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { WafStatus } from './types/waf'; const API = { getWaf: (): Promise< WafStatus > => diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 2d023560517f3..68f9359a9bd81 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useNotices from '../../hooks/use-notices'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; import ScanButton from '../scan-button'; @@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => { const { isRegistered } = useConnection(); const { isSeen: wafSeen } = useWafData(); const navigate = useNavigate(); - const { - counts: { - current: { threats: numThreats }, - }, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); const location = useLocation(); const { hasPlan } = usePlan(); @@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => { link="/scan" label={ - { numThreats > 0 + { status.threats.length > 0 ? sprintf( // translators: %d is the number of threats found. __( 'Scan (%d)', 'jetpack-protect' ), - numThreats + status.threats.length ) : __( 'Scan', 'jetpack-protect' ) } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adc0cee561ba5..da2e9510cd7d9 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -5,10 +5,21 @@ .header { display: flex; justify-content: space-between; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px + align-items: center; + + @media ( min-width: 600px ) { + flex-direction: row; + } &__scan_buttons { display: flex; gap: calc( var( --spacing-base ) * 3 ); // 24px + + @media ( min-width: 600px ) { + flex-direction: row; + } } } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 5214531dcf362..1a9bc87387fa9 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; -import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; interface ErrorAdminSectionHeroProps { @@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { { displayErrorMessage } -
- -
} preserveSecondaryOnMobile={ false } diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
- +
diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
- -
- { children } -
-
- ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
{ children }
-
- ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
- -
- ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx index 7e8113b6f38ab..0788eb8bd7a41 100644 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx @@ -1,16 +1,18 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
- { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
- +
diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx deleted file mode 100644 index 93ebecf7235ef..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/badge.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, check, info } from '@wordpress/icons'; -import PropTypes from 'prop-types'; -import React, { useState, useCallback, useMemo } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import styles from './styles.module.scss'; - -/** - * Gets the Badge element - * - * @param {number} count - The number of threats found for this item. - * @param {boolean} checked - Whether this item was checked for threats yet. - * @return {object} The badge element - */ -const getBadgeElement = ( count, checked ) => { - if ( ! checked ) { - return { - popoverText: __( - 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', - 'jetpack-protect' - ), - badgeElement: ( - - ), - }; - } - - if ( count === 0 ) { - return { - popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ), - badgeElement: ( - - ), - }; - } - - return { - popoverText: null, - badgeElement: ( - - { count } - - ), - }; -}; - -const ItemBadge = ( { count, checked } ) => { - const { data: status } = useScanStatusQuery(); - - const { popoverText, badgeElement } = getBadgeElement( count, checked ); - const [ showPopover, setShowPopover ] = useState( false ); - - const inProgress = useMemo( () => isScanInProgress( status ), [ status ] ); - - const handleEnter = useCallback( () => { - if ( inProgress ) { - return; - } - - setShowPopover( true ); - }, [ inProgress ] ); - - const handleOut = useCallback( () => { - setShowPopover( false ); - }, [] ); - - return ( -
- { ! inProgress ? badgeElement : } - { showPopover && ( - - - { popoverText } - - - ) } -
- ); -}; - -ItemBadge.propTypes = { - /* The number of threats found for this item */ - count: PropTypes.number, - /* Whether this item was checked for threats yet */ - checked: PropTypes.bool, -}; - -export default ItemBadge; diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx deleted file mode 100644 index 9352ae5c63d67..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/group.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useState, useCallback, useContext } from 'react'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const MAX_ITEMS = 8; - -const NavigationGroup = ( { icon, label, children } ) => { - const [ collapsed, setCollapsed ] = useState( true ); - const { mode } = useContext( NavigationContext ); - const needsTruncate = - Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list'; - const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children; - const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0; - - const handleCollapsedToggle = useCallback( () => { - setCollapsed( current => ! current ); - }, [] ); - - return ( -
  • - - { label } - -
    -
      { content }
    - { needsTruncate && ( -
    - -
    - ) } -
    -
  • - ); -}; - -export default NavigationGroup; diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx deleted file mode 100644 index bd30dfbdad964..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import React, { useState, useRef, useCallback } from 'react'; -import NavigationGroup from './group'; -import NavigationItem from './item'; -import styles from './styles.module.scss'; -import useMenuNavigation, { NavigationContext } from './use-menu-navigation'; - -const NavigationList = ( { children } ) => ( -
      - { children } -
    -); - -const NavigationDropdown = ( { children, data } ) => { - const ref = useRef( undefined ); - const [ listOpen, setListOpen ] = useState( false ); - const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? { - label: __( 'See all results', 'jetpack-protect' ), - }; - const { label, icon } = item; - - const handleOpen = useCallback( () => { - setListOpen( open => ! open ); - }, [] ); - - return ( - - ); -}; - -const getNavigationComponent = mode => { - switch ( mode ) { - case 'list': - return NavigationList; - case 'dropdown': - return NavigationDropdown; - default: - return NavigationList; - } -}; - -const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => { - const data = useMenuNavigation( { selected, onSelect } ); - const Component = getNavigationComponent( mode ); - - return ( - - { children } - - ); -}; - -export default Navigation; -export { NavigationItem, NavigationGroup }; diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx deleted file mode 100644 index d902625c3997d..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/item.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import clsx from 'clsx'; -import React, { useContext, useEffect, useCallback } from 'react'; -import ItemBadge from './badge'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const NavigationItem = ( { - id, - label, - icon, - badge, - disabled, - onClick, - onKeyDown, - onFocus, - checked, -} ) => { - const context = useContext( NavigationContext ); - - const selected = context?.selectedItem === id; - const registerItem = context?.registerItem; - const registerRef = context?.registerRef; - const handleClickItem = context?.handleClickItem; - const handleKeyDownItem = context?.handleKeyDownItem; - const handleFocusItem = context?.handleFocusItem; - - const wrapperClassName = clsx( styles[ 'navigation-item' ], { - [ styles.clickable ]: ! disabled, - [ styles.selected ]: selected, - } ); - - const handleClick = useCallback( - evt => { - onClick?.( evt ); - handleClickItem?.( id ); - }, - [ handleClickItem, id, onClick ] - ); - - const handleKeyDown = useCallback( - evt => { - onKeyDown?.( evt ); - handleKeyDownItem?.( evt ); - }, - [ handleKeyDownItem, onKeyDown ] - ); - - const handleRef = useCallback( - ref => { - registerRef( ref, id ); - }, - [ registerRef, id ] - ); - - const handleFocus = useCallback( - evt => { - onFocus?.( evt ); - handleFocusItem?.( id ); - }, - [ handleFocusItem, id, onFocus ] - ); - - useEffect( () => { - registerItem( { id, disabled, label, icon } ); - // eslint-disable-next-line - }, [] ); - - return ( -
  • - { label } - -
  • - ); -}; - -export default NavigationItem; diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx deleted file mode 100644 index 8f075caae020a..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/label.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon } from '@wordpress/icons'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './styles.module.scss'; - -const ItemLabel = ( { icon, children, className } ) => { - return ( - - { icon && } - { children } - - ); -}; - -ItemLabel.propTypes = { - /* An icon that will be rendered before text */ - icon: PropTypes.node, - /* Label text that will be rendered */ - children: PropTypes.node.isRequired, -}; - -export default ItemLabel; diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss deleted file mode 100644 index df9e3ef1f8a25..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss +++ /dev/null @@ -1,142 +0,0 @@ -.navigation { - background-color: var( --jp-white ); - box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); - border-radius: var( --jp-border-radius ); - margin: 0; -} - -.navigation-item { - display: flex; - padding: calc( var( --spacing-base ) * 2 ); // 16px; - align-items: center; - justify-content: space-between; - margin: 0; - text-align: left; - - // Clickable State - &.clickable { - cursor: pointer; - outline-color: var( --jp-black ); - - // Focus/Hover State - &:hover:not(.selected), - &:focus:not(.selected) { - background-color: var( --jp-gray-0 ); - } - } - - // Selected State - &.selected { - background-color: var( --jp-black ); - - & .navigation-item-label { - color: var( --jp-white ) - } - - & .navigation-item-icon { - fill: var( --jp-white ); - } - - & .navigation-item-badge { - border: 1px solid var( --jp-red ); - background-color: var( --jp-red ); - color: var( --jp-white ); - } - } - - // CHILDRENS - - // .navigation-item-label - &-label { - display: flex; - align-items: center; - padding-right: var( --spacing-base ); // 8px - overflow-x: hidden; - } - - // .navigation-item-label-content - &-label-text { - display: block; - overflow-x: hidden; - text-overflow: ellipsis; - } - - // .navigation-item-icon - &-icon { - margin-right: calc( var( --spacing-base ) * 2); // 16px - } - - // .navigation-item-badge - &-badge { - border: 1px solid var( --jp-red-60 ); - color: var( --jp-red-60 ); - border-radius: 50%; - padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px - min-width: 30px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - &-check-badge { - fill: var( --jp-green-50 ); - } - - &-info-badge { - fill: var( --jp-gray-20 ); - } -} - -.navigation-group { - --icon-size: 28px; - --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px - --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px - - list-style: none; - - &-label { - padding: calc( var( --spacing-base ) * 2 ); // 16px - } - - &-content { - padding: 0; - } - - &-list { - margin-left: var( --left-spacing ) ; - } - - &-truncate { - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - justify-content: flex-start; - } -} - -.popover-text { - width: 250px; - padding: calc( var( --spacing-base ) * 2 ); // 16px -} - -.navigation-dropdown { - &-button { - display: flex; - border: 1px solid var( --jp-gray-10 ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - background-color: var( --jp-white ); - justify-content: space-between; - align-items: center; - width: 100%; - } - - &-label { - display: flex; - justify-content: flex-start; - } - - &-icon { - margin-right: var( --spacing-base ); // 8px - } -} diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js deleted file mode 100644 index 2972dac06b572..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -export const NavigationContext = React.createContext(); - -const useMenuNavigation = ( { selected, onSelect } ) => { - const [ items, setItems ] = useState( [] ); - const [ refs, setRef ] = useState( [] ); - const [ focusedItem, setFocusedItem ] = useState(); - - const handleClickItem = id => { - onSelect( id ); - }; - - const handleFocusItem = id => { - setFocusedItem( id ); - }; - - const getPrevItem = ( current, last ) => { - const startMinusOne = current - 1; - const prevIndex = startMinusOne < 0 ? last : startMinusOne; - const prevItem = items[ prevIndex ]; - return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem; - }; - - const getNextItem = ( current, last ) => { - const startPlusOne = current + 1; - const nextIndex = startPlusOne > last ? 0 : startPlusOne; - const nextItem = items[ nextIndex ]; - return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem; - }; - - const handleKeyDownItem = input => { - const code = input?.code; - const current = items.findIndex( item => item?.id === selected ); - const lastIndex = items.length - 1; - - let nextId; - - if ( code === 'ArrowUp' ) { - const prevItem = getPrevItem( current, lastIndex ); - nextId = prevItem?.id; - } else if ( code === 'ArrowDown' ) { - const nextItem = getNextItem( current, lastIndex ); - nextId = nextItem?.id; - } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) { - nextId = focusedItem; - } - - if ( nextId ) { - const element = refs[ nextId ]; - element?.focus(); - onSelect( nextId ); - } - }; - - const registerRef = ( ref, id ) => { - setRef( allRefs => { - if ( ! allRefs[ id ] && ref ) { - return { ...allRefs, [ id ]: ref }; - } - return allRefs; - } ); - }; - - const registerItem = data => { - setItems( allItems => { - const newItems = [ ...allItems ]; - const id = data?.id; - const currentIdx = newItems.findIndex( item => item?.id === id ); - - if ( currentIdx >= 0 ) { - newItems[ currentIdx ] = data; - } else { - newItems.push( data ); - } - - return newItems; - } ); - }; - - return { - selectedItem: selected, - handleClickItem, - handleKeyDownItem, - handleFocusItem, - registerRef, - registerItem, - items, - }; -}; - -export default useMenuNavigation; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index c733ff1f0a08c..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - IconTooltip, - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
    - { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
    -); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - - - { createInterpolateElement( - __( - 'The fixer is taking longer than expected. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - - - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 8304942b206d4..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,202 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -.icon-tooltip { - max-height: 20px; - margin-left: calc( var( --spacing-base ) / 2 ); // 4px - - &__icon { - color: var( --jp-red ); - } - - &__content { - color: var( --jp-gray-70 ); - font-weight: 400; - line-height: 24px; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx deleted file mode 100644 index d1100d8ce6d5e..0000000000000 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ProtectCheck(): JSX.Element { - return ( - - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx deleted file mode 100644 index e626b6af066c7..0000000000000 --- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import usePlan from '../../hooks/use-plan'; -import ButtonGroup from '../button-group'; - -/** - * Navigation for scan sections. - * - * @return {React.Element} The React Component. - */ -export default function ScanNavigation() { - const navigate = useNavigate(); - const location = useLocation(); - const { hasPlan } = usePlan(); - - const viewingScanPage = location.pathname === '/scan'; - const viewingHistoryPage = location.pathname.includes( '/scan/history' ); - const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] ); - const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] ); - - if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) { - return null; - } - - return ( - <> - - - { __( 'Scanner', 'jetpack-protect' ) } - - - { __( 'History', 'jetpack-protect' ) } - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..45a8524e60b59 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,6 +1,7 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; import styles from './styles.module.scss'; @@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } ) return ( <>
    - +
    - { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
    - ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
    - - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
    - ) } - { fixedIn && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
    - ) } - { ! description &&
    { learnMoreButton }
    } -
    - ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
    - -
    - { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
    - { getTitle() } - { hasPlan && ( -
    - { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
    - ) } -
    - { hasPlan ? ( - <> -
    - -
    - - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
    -
    - { ! scanning && ( -
    - ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
    - - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
    - ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
    { filename }
    - - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
    - ) } - { ! description &&
    { learnMoreButton }
    } - { [ 'ignored', 'current' ].includes( status ) && ( -
    - { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
    - ) } -
    - ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
    - { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
    - ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index 2338d306e6780..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx index 53af919b17f78..52ec1333bd958 100644 --- a/projects/plugins/protect/src/js/routes/firewall/index.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx @@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; -import ScanFooter from '../scan/scan-footer'; import FirewallAdminSectionHero from './firewall-admin-section-hero'; import FirewallFooter from './firewall-footer'; import styles from './styles.module.scss'; @@ -597,7 +596,7 @@ const FirewallPage = () => {
    - { wafSupported ? : } + { wafSupported && } ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 141c51cde284a..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previous %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previous threats', 'jetpack-protect' ) } - - - - { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } - - - - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
    -
    - { getTitle() } -
    - -
    -
    - -
    - ) : ( - <> -
    -
    - -
    -
    -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
    - - ) } - -
    - -
    -
    - ) } - -
    - ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index d30f3e0ac3344..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..c56ae3c747f3e 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,16 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import OnboardingPopover from '../../components/onboarding-popover'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; -import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; +import styles from './styles.module.scss'; /** * Scan Page @@ -19,23 +21,41 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); + const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: [ 'current' ], + operator: 'isAny', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,16 +69,33 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } - + + + +
    + +
    + { !! status && ! isScanInProgress( status ) && ( + + ) } + { !! status && ! isScanInProgress( status ) && hasPlan && ( + + ) } + +
    +
    ); diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx index 0e85aa56d9289..c29af26bcb409 100644 --- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx +++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx @@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan'; const { siteSuffix } = window.jetpackProtectInitialState; -const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' ); -const scanResultsDescription = ( - - { __( - 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files', - 'jetpack-protect' - ) } - -); const UpgradeButton = props => { const { upgradePlan } = usePlan(); const { recordEvent } = useAnalyticsTracks(); @@ -27,11 +18,6 @@ const UpgradeButton = props => { }; export default [ - { - id: 'free-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, - }, { id: 'free-daily-scans', title: __( 'Daily automated scans', 'jetpack-protect' ), @@ -49,10 +35,41 @@ export default [ ), }, + { + id: 'paid-daily-and-manual-scans', + title: __( 'Daily & manual scanning', 'jetpack-protect' ), + description: ( + + { __( + 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', + 'jetpack-protect' + ) } + + ), + }, + { + id: 'free-scan-results', + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.', + 'jetpack-protect' + ) } + + ), + }, { id: 'paid-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.', + 'jetpack-protect' + ) } + + ), }, { id: 'paid-fix-all-threats', @@ -97,16 +114,4 @@ export default [ ), }, - { - id: 'paid-daily-and-manual-scans', - title: __( 'Daily & manual scanning', 'jetpack-protect' ), - description: ( - - { __( - 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', - 'jetpack-protect' - ) } - - ), - }, ]; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 9e1b9c102a037..db76bac1b15b0 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,33 +1,41 @@ import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); - const { hasPlan } = usePlan(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); - const { list } = useThreatsList(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const { setModal } = useModal(); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); + + const { globalStats } = useWafData(); + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); + + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); @@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => { // List of fixable threats that do not have a fix in progress const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; return ( threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) ); } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } const handleShowAutoFixersClick = threatList => { @@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => { ) : __( 'Most recent results', 'jetpack-protect' ) } - { ! hasPlan && ( - - ) } + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( @@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => { ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) ) - : sprintf( - /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No active %s', 'jetpack-protect' ), - hasPlan - ? __( 'threats', 'jetpack-protect' ) - : __( - 'vulnerabilities', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) - ) } + : __( "Don't worry about a thing", 'jetpack-protect' ) } <> - - { __( - 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', - 'jetpack-protect' - ) } - - { fixableList.length > 0 && ( + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } + + ) : ( <> - - { ! scanning && ( - -
    - -
    } secondary={ } diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 908e34f6e71d7..163fd23248aaa 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,12 +1,14 @@ -.subheading-text { - white-space: nowrap; +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.product-section, .info-section { - margin-top: calc( var( --spacing-base ) * 7 ); // 56px - margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px -} +.scan-results-container { + padding-left: 0; + padding-right: 0; + overflow: hidden; -.auto-fixers { - margin-top: calc( var( --spacing-base ) * 4 ); // 32px -} \ No newline at end of file + > * { + margin-left: calc( var( --spacing-base ) * -3 ); // -24px + margin-right: calc( var( --spacing-base ) * -3 ); // -24px + } +} diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..0c65dfec146a7 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -33,6 +33,24 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + /** + * Transpile @wordpress/dataviews in node_modules too. + * + * @see https://github.com/Automattic/jetpack/issues/39907 + */ + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress/dataviews/' ], + babelOpts: { + configFile: false, + plugins: [ + [ + require.resolve( '@automattic/babel-plugin-replace-textdomain' ), + { textdomain: 'jetpack-protect' }, + ], + ], + }, + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From 28310a81d3c0e48a173a9bd122ab9ce43a0c86e7 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 13:09:37 -0800 Subject: [PATCH 076/290] Hook up ThreatModal integration in Protect --- .../threat-modal/threat-fix-confirmation.tsx | 1 - .../components/threats-data-views/index.tsx | 2 +- .../threats-data-views/styles.module.scss | 4 + projects/js-packages/scan/src/utils/index.ts | 18 +++-- .../js/routes/scan/scan-results-data-view.tsx | 73 ++++++++++++++----- 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index 73a9567988cb3..630701a351335 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -22,7 +22,6 @@ const ThreatFixConfirmation = () => { { [ 'all', 'fix' ].includes( actionToConfirm ) && } - { /* TODO: Necessary to show ignore confirmation in all view? */ } { [ 'all', 'ignore' ].includes( actionToConfirm ) && } { siteCredentialsNeeded && userConnectionNeeded && ( { return __( 'Delete file', 'jetpack-scan' ); } - if ( threat.extension?.type === 'plugin' ) { + if ( threat.extension?.type === 'plugins' ) { + // TODO: Update this when it changes return __( 'Delete plugin from site', 'jetpack-scan' ); } - if ( threat.extension?.type === 'theme' ) { + if ( threat.extension?.type === 'themes' ) { + // TODO: Update this when it changes return __( 'Delete theme from site', 'jetpack-scan' ); } break; case 'update': - if ( threat.extension?.type === 'plugin' ) { + if ( threat.extension?.type === 'plugins' ) { + // TODO: Update this when it changes return __( 'Update plugin to newer version', 'jetpack-scan' ); } - if ( threat.extension?.type === 'theme' ) { + if ( threat.extension?.type === 'themes' ) { + // TODO: Update this when it changes return __( 'Update theme to newer version', 'jetpack-scan' ); } return __( 'Update', 'jetpack-scan' ); @@ -145,11 +149,13 @@ export const getFixerDescription = ( threat: Threat ) => { return __( 'Delete the infected file.', 'jetpack-scan' ); } - if ( threat.extension?.type === 'plugin' ) { + if ( threat.extension?.type === 'plugins' ) { + // TODO: Update this when it changes return __( 'Delete the plugin directory to fix the threat.', 'jetpack-scan' ); } - if ( threat.extension?.type === 'theme' ) { + if ( threat.extension?.type === 'themes' ) { + // TODO: Update this when it changes return __( 'Delete the theme directory to fix the threat.', 'jetpack-scan' ); } break; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx index 616f4e256ed44..f291fd2b2bef8 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx @@ -1,9 +1,15 @@ -import { ThreatsDataViews } from '@automattic/jetpack-components'; +import { ThreatsDataViews, getRedirectUrl } from '@automattic/jetpack-components'; +import { useConnection } from '@automattic/jetpack-connection'; import { Threat } from '@automattic/jetpack-scan'; import { useCallback } from 'react'; import useHistoryQuery from '../../data/scan/use-history-query'; +import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useModal from '../../hooks/use-modal'; +import useUnIgnoreThreatMutation from '../../data/scan/use-unignore-threat-mutation'; +import useCredentialsQuery from '../../data/use-credentials-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; +import useFixers from '../../hooks/use-fixers'; +import usePlan from '../../hooks/use-plan'; /** * Scan Results Data View @@ -18,39 +24,68 @@ export default function ScanResultsDataView( { }: { filters: React.ComponentProps< typeof ThreatsDataViews >[ 'filters' ]; } ) { - const { setModal } = useModal(); - + const { siteSuffix, blogID } = window.jetpackProtectInitialState; const { data: scanStatus } = useScanStatusQuery(); const { data: history } = useHistoryQuery(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { fixThreats } = useFixers(); + const ignoreThreatMutation = useIgnoreThreatMutation(); + const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); + const { isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser } = useConnection( + { + redirectUri: 'admin.php?page=jetpack-protect', + from: 'scan', + autoTrigger: false, + skipUserConnection: false, + skipPricingPage: true, + } + ); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_threat_modal_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); - const onFixThreats = useCallback( - ( threats: Threat[] ) => { - setModal( { type: 'FIX_THREAT', props: { threat: threats[ 0 ] } } ); + const handleFixClick = useCallback( + async ( threats: Threat[] ) => { + await fixThreats( [ threats[ 0 ].id as number ] ); }, - [ setModal ] + [ fixThreats ] ); - const onIgnoreThreats = useCallback( - ( threats: Threat[] ) => { - setModal( { type: 'IGNORE_THREAT', props: { threat: threats[ 0 ] } } ); + const handleIgnoreClick = useCallback( + async ( threats: Threat[] ) => { + await ignoreThreatMutation.mutateAsync( threats[ 0 ].id ); }, - [ setModal ] + [ ignoreThreatMutation ] ); - const onUnignoreThreats = useCallback( - ( threats: Threat[] ) => { - setModal( { type: 'UNIGNORE_THREAT', props: { threat: threats[ 0 ] } } ); + const handleUnignoreClick = useCallback( + async ( threats: Threat[] ) => { + await unignoreThreatMutation.mutateAsync( threats[ 0 ].id ); }, - [ setModal ] + [ unignoreThreatMutation ] ); return ( ); } From 3fc4a41ec0130a04bccd84a40246dc4820818415 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 13:17:35 -0800 Subject: [PATCH 077/290] Revert changes to scan types until the expected type changes on the backend --- projects/js-packages/scan/src/utils/index.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 521b9047ddc7f..29e017c837b68 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -98,23 +98,19 @@ export const getDetailedFixerAction = ( threat: Threat ) => { return __( 'Delete file', 'jetpack-scan' ); } - if ( threat.extension?.type === 'plugins' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'plugin' ) { return __( 'Delete plugin from site', 'jetpack-scan' ); } - if ( threat.extension?.type === 'themes' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'theme' ) { return __( 'Delete theme from site', 'jetpack-scan' ); } break; case 'update': - if ( threat.extension?.type === 'plugins' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'plugin' ) { return __( 'Update plugin to newer version', 'jetpack-scan' ); } - if ( threat.extension?.type === 'themes' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'theme' ) { return __( 'Update theme to newer version', 'jetpack-scan' ); } return __( 'Update', 'jetpack-scan' ); @@ -149,13 +145,11 @@ export const getFixerDescription = ( threat: Threat ) => { return __( 'Delete the infected file.', 'jetpack-scan' ); } - if ( threat.extension?.type === 'plugins' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'plugin' ) { return __( 'Delete the plugin directory to fix the threat.', 'jetpack-scan' ); } - if ( threat.extension?.type === 'themes' ) { - // TODO: Update this when it changes + if ( threat.extension?.type === 'theme' ) { return __( 'Delete the theme directory to fix the threat.', 'jetpack-scan' ); } break; From 77fc70d67214a953473e25409c81f0ed08abef18 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 13:35:10 -0800 Subject: [PATCH 078/290] Add dummy arg --- .../components/threat-modal/threat-technical-details.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 0e46fb8c38f6f..83c58e45719b5 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -39,7 +39,11 @@ const ThreatTechnicalDetails = (): JSX.Element => { { open ? __( 'Hide the technical details', 'jetpack-components' ) - : __( 'Show the technical details', 'jetpack-components' ) } + : __( + 'Show the technical details', + 'jetpack-components', + /* dummy arg to avoid bad minification */ 0 + ) }
    From 39675ef0d0c65f0117319a35f2a24c6a3f80a9c8 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 14:09:43 -0800 Subject: [PATCH 079/290] Add credentials polling while modal is open --- .../components/threats-data-views/index.tsx | 13 +++++++-- .../js/routes/scan/scan-results-data-view.tsx | 28 ++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index cb9534983193d..4e5debce0ab8d 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -64,7 +64,8 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {object[]} props.credentials - The credentials. * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. - * + * @param {Function} props.onModalOpen - Callback function on modal open. + * @param {Function} props.onModalClose - Callback function on modal close. * @return {JSX.Element} The ThreatsDataViews component. */ export default function ThreatsDataViews( { @@ -85,6 +86,8 @@ export default function ThreatsDataViews( { credentials, credentialsIsFetching, credentialsRedirectUrl, + onModalOpen, + onModalClose, }: { data: Threat[]; filters?: Filter[]; @@ -103,6 +106,8 @@ export default function ThreatsDataViews( { credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; + onModalOpen: () => void; + onModalClose: () => void; } ): JSX.Element { const baseView = { sort: { @@ -173,16 +178,18 @@ export default function ThreatsDataViews( { const showThreatModal = useCallback( ( threat: Threat, action: string ) => () => { + onModalOpen?.(); setOpenThreat( threat ); setActionToConfirm( action ); }, - [] + [ onModalOpen ] ); const hideThreatModal = useCallback( () => { + onModalClose?.(); setOpenThreat( null ); setActionToConfirm( 'all' ); - }, [] ); + }, [ onModalClose ] ); /** * Compute values from the provided threats data. diff --git a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx index f291fd2b2bef8..a6a60b0572d38 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx @@ -1,7 +1,9 @@ import { ThreatsDataViews, getRedirectUrl } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { Threat } from '@automattic/jetpack-scan'; -import { useCallback } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { useCallback, useEffect, useState } from 'react'; +import { QUERY_CREDENTIALS_KEY } from '../../constants'; import useHistoryQuery from '../../data/scan/use-history-query'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useScanStatusQuery from '../../data/scan/use-scan-status-query'; @@ -25,6 +27,8 @@ export default function ScanResultsDataView( { filters: React.ComponentProps< typeof ThreatsDataViews >[ 'filters' ]; } ) { const { siteSuffix, blogID } = window.jetpackProtectInitialState; + const queryClient = useQueryClient(); + const { data: scanStatus } = useScanStatusQuery(); const { data: history } = useHistoryQuery(); const { recordEvent } = useAnalyticsTracks(); @@ -43,6 +47,10 @@ export default function ScanResultsDataView( { } ); + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const onModalOpen = useCallback( () => setIsModalOpen( true ), [] ); + const onModalClose = useCallback( () => setIsModalOpen( false ), [] ); + const getScan = useCallback( () => { recordEvent( 'jetpack_protect_threat_modal_get_scan_link_click' ); upgradePlan(); @@ -69,6 +77,22 @@ export default function ScanResultsDataView( { [ unignoreThreatMutation ] ); + /** + * Poll credentials as long as the modal is open. + */ + useEffect( () => { + if ( ! isModalOpen ) { + return; + } + const interval = setInterval( () => { + if ( ! credentials || credentials.length === 0 ) { + queryClient.invalidateQueries( { queryKey: [ QUERY_CREDENTIALS_KEY ] } ); + } + }, 5_000 ); + + return () => clearInterval( interval ); + }, [ isModalOpen, queryClient, credentials ] ); + return ( ); } From c3fa03fc75c5abd2873be6ec1c9713450fa5a569 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 14:12:42 -0800 Subject: [PATCH 080/290] Improve organization --- .../protect/src/js/routes/scan/scan-results-data-view.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx index a6a60b0572d38..d042cad25015f 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx @@ -31,11 +31,14 @@ export default function ScanResultsDataView( { const { data: scanStatus } = useScanStatusQuery(); const { data: history } = useHistoryQuery(); + const { recordEvent } = useAnalyticsTracks(); const { hasPlan, upgradePlan } = usePlan(); + const { fixThreats } = useFixers(); const ignoreThreatMutation = useIgnoreThreatMutation(); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); const { isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser } = useConnection( { From d24d49291b3b99ec35e08d903a6c5127ca1b0a61 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 5 Dec 2024 14:17:05 -0800 Subject: [PATCH 081/290] Fix tests --- .../components/threats-data-views/test/index.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx index c9c83afd24f68..2b298bf5a4b02 100644 --- a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx +++ b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx @@ -62,6 +62,8 @@ const mockProps = { credentials: [], credentialsIsFetching: false, credentialsRedirectUrl: '/redirect-url', + onModalOpen: () => {}, + onModalClose: () => {}, }; describe( 'ThreatsDataViews', () => { From 5ce9a33a24e39fca6734341320c175933ba7538b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 6 Dec 2024 05:27:48 -0800 Subject: [PATCH 082/290] Remove dummy arg --- .../components/threat-modal/threat-technical-details.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 83c58e45719b5..0e46fb8c38f6f 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -39,11 +39,7 @@ const ThreatTechnicalDetails = (): JSX.Element => { { open ? __( 'Hide the technical details', 'jetpack-components' ) - : __( - 'Show the technical details', - 'jetpack-components', - /* dummy arg to avoid bad minification */ 0 - ) } + : __( 'Show the technical details', 'jetpack-components' ) }
    From 49db999853812b91cca0f74063d83d05b334e018 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 6 Dec 2024 05:51:01 -0800 Subject: [PATCH 083/290] Fix build errors --- .../components/threat-modal/threat-technical-details.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 0e46fb8c38f6f..e2b7c5caa70eb 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -17,6 +17,11 @@ const ThreatTechnicalDetails = (): JSX.Element => { const [ open, setOpen ] = useState( false ); + let toggleContent = __( 'Show the technical details', 'jetpack-components' ); + if ( open ) { + toggleContent = __( 'Hide the technical details', 'jetpack-components' ); + } + const toggleOpen = useCallback( () => { setOpen( ! open ); }, [ open ] ); @@ -37,9 +42,7 @@ const ThreatTechnicalDetails = (): JSX.Element => { >
    - { open - ? __( 'Hide the technical details', 'jetpack-components' ) - : __( 'Show the technical details', 'jetpack-components' ) } + { toggleContent }
    From 4dd60b7cc3dd955f440fe39f85617591d1d19a87 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 14 Nov 2024 10:42:12 -0700 Subject: [PATCH 084/290] Init project branch From ff92d887df17e588780bcec47025fbcb6a0807a9 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:03:36 -0800 Subject: [PATCH 085/290] Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller --- .../changelog/add-protect-header-buttons | 4 +++ .../src/js/components/admin-page/index.jsx | 27 +++++++++++++++++-- .../components/admin-page/styles.module.scss | 10 +++++++ .../src/js/components/scan-button/index.jsx | 4 +++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/protect/changelog/add-protect-header-buttons diff --git a/projects/plugins/protect/changelog/add-protect-header-buttons b/projects/plugins/protect/changelog/add-protect-header-buttons new file mode 100644 index 0000000000000..24c40f542d7ee --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-header-buttons @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Adds Go to Cloud and Scan now buttons to the primary header diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4579831b5f0a5..4e93ae443aa72 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -1,16 +1,20 @@ import { AdminPage as JetpackAdminPage, + Button, Container, + getRedirectUrl, JetpackProtectLogo, } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import useNotices from '../../hooks/use-notices'; +import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; +import ScanButton from '../scan-button'; import Tabs, { Tab } from '../tabs'; import styles from './styles.module.scss'; @@ -24,6 +28,8 @@ const AdminPage = ( { children } ) => { current: { threats: numThreats }, }, } = useProtectData(); + const location = useLocation(); + const { hasPlan } = usePlan(); // Redirect to the setup page if the site is not registered. useEffect( () => { @@ -36,10 +42,27 @@ const AdminPage = ( { children } ) => { return null; } + const viewingScanPage = location.pathname.includes( '/scan' ); + + const { siteSuffix, blogID } = window.jetpackProtectInitialState || {}; + const goToCloudUrl = getRedirectUrl( 'jetpack-scan-dash', { site: blogID ?? siteSuffix } ); + return ( } + header={ +
    + + { hasPlan && viewingScanPage && ( +
    + + +
    + ) } +
    + } > { notice && } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index e70d2cdb076c7..adf7dc594b907 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From 39e40946052c87f10efe686366f80dbceb0cc8bb Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 086/290] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 9c8f30b7b8067..4aa517f5f120b 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import AdminSectionHero from '../../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } + { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..1c5cc6cac49b9 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; +import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useFixers from '../../hooks/use-fixers'; +import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return list.filter( threat => { + const threatId = parseInt( threat.id ); + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From 9fec01117ca876cd5eb57d8134890875151290a0 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 087/290] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
    - diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adf7dc594b907..adc0cee561ba5 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From af172c93254adce796c17104f8ab76d4d518b5c3 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 29 Nov 2024 21:00:37 -0700 Subject: [PATCH 088/290] Protect: add ShieldIcon component --- .../components/admin-section-hero/index.tsx | 21 ++- .../stories/index.stories.jsx | 4 +- .../src/js/components/shield-icon/index.tsx | 165 ++++++++++++++++++ .../shield-icon/stories/index.stories.tsx | 50 ++++++ 4 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ed83bebc8638..758c8c21e0193 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,9 +1,6 @@ -import { - AdminSectionHero as JetpackAdminSectionHero, - H3, - getIconBySlug, -} from '@automattic/jetpack-components'; +import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; +import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -15,7 +12,7 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean } >; + Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -44,17 +41,23 @@ const AdminSectionHero: AdminSectionHeroComponent = ( { AdminSectionHero.Heading = ( { children, + variant = 'default', showIcon = false, }: { children: React.ReactNode; + variant?: 'default' | 'success' | 'error'; showIcon?: boolean; } ) => { - const Icon = getIconBySlug( 'protect' ); - return (

    { children } - { showIcon && } + { showIcon && ( + + ) }

    ); }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index 7d5b4f8066c93..ca2dfda7fc98e 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -12,7 +12,9 @@ Default.args = { main: ( <> - { 'No threats found' } + + { 'No threats found' } + { 'Most recent results' } diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..3bf7f479f0051 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/index.tsx @@ -0,0 +1,165 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.variant - Icon variant. + * @param {string} props.fill - Icon fill color. + * @param {string} props.className - Additional class names. + * @param {number} props.height - Icon height. + * @return {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ShieldIcon( { + variant = 'default', + height = 32, + className, + fill, +}: { + variant: + | 'default' + | 'success' + | 'error' + | 'default-outline' + | 'success-outline' + | 'error-outline'; + className?: string; + height?: number; + fill?: string; +} ): JSX.Element { + if ( 'error-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'error' === variant ) { + return ( + + + + + ); + } + + if ( 'success-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'success' === variant ) { + return ( + + + + + ); + } + + if ( 'default-outline' === variant ) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..d10365f4b0834 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ShieldIcon from '../index'; + +export default { + title: 'Plugins/Protect/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
    + +
    + ), + ], + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ + 'default', + 'success', + 'error', + 'default-outline', + 'success-outline', + 'error-outline', + ], + }, + fill: { + control: 'color', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'default', +}; + +export const SuccessVariant = args => ; +SuccessVariant.args = { + variant: 'success', +}; + +export const ErrorVariant = args => ; +ErrorVariant.args = { + variant: 'error', +}; From 00283bca9a70e694e8ca0680eace84279e7bb624 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 4 Dec 2024 20:26:54 -0700 Subject: [PATCH 089/290] Protect: Add ShieldIcon Component (#40402) --- .../components/changelog/add-shield-icon | 4 + .../components/shield-icon/index.tsx | 79 +++++++++ .../shield-icon/stories/index.stories.tsx | 54 ++++++ projects/js-packages/components/index.ts | 1 + .../protect/changelog/refactor-alert-icon | 5 + .../components/admin-section-hero/index.tsx | 20 ++- .../admin-section-hero/styles.module.scss | 4 +- .../src/js/components/alert-icon/index.jsx | 74 -------- .../alert-icon/stories/index.stories.jsx | 17 -- .../components/alert-icon/styles.module.scss | 11 -- .../src/js/components/shield-icon/index.tsx | 165 ------------------ .../shield-icon/stories/index.stories.tsx | 50 ------ .../history/history-admin-section-hero.tsx | 2 +- .../routes/scan/scan-admin-section-hero.tsx | 2 +- 14 files changed, 162 insertions(+), 326 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-shield-icon create mode 100644 projects/js-packages/components/components/shield-icon/index.tsx create mode 100644 projects/js-packages/components/components/shield-icon/stories/index.stories.tsx create mode 100644 projects/plugins/protect/changelog/refactor-alert-icon delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/js-packages/components/changelog/add-shield-icon b/projects/js-packages/components/changelog/add-shield-icon new file mode 100644 index 0000000000000..5c6cc27eeb809 --- /dev/null +++ b/projects/js-packages/components/changelog/add-shield-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add ShieldIcon component diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..fee9f4d70c463 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +const COLORS = { + error: '#D63638', + warning: '#F0B849', + success: '#069E08', + default: '#1d2327', +}; + +/** + * Protect Shield SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.className - Additional class names. + * @param {string} props.contrast - Icon contrast color. Overrides variant. + * @param {string} props.fill - Icon fill color (default, success, warning, error, or a custom color code string). Overrides variant. + * @param {number} props.height - Icon height (px). Width is calculated based on height. + * @param {string} props.icon - Icon variant (success, error). Overrides variant. + * @param {boolean} props.outline - When enabled, the icon will use an outline style. + * @param {string} props.variant - Icon variant (default, success, error). + * + * @return {React.ReactElement} Protect Shield SVG Icon + */ +export default function ShieldIcon( { + className, + contrast = '#fff', + fill, + height = 32, + icon, + outline = false, + variant = 'default', +}: { + className?: string; + contrast?: string; + fill?: 'default' | 'success' | 'warning' | 'error' | string; + height?: number; + icon?: 'success' | 'error'; + outline?: boolean; + variant: 'default' | 'success' | 'warning' | 'error'; +} ): JSX.Element { + const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; + const iconFill = outline ? shieldFill : contrast; + const iconVariant = icon || variant; + + return ( + + + { 'success' === iconVariant && ( + + ) } + { [ 'warning', 'error' ].includes( iconVariant ) && ( + + ) } + + ); +} diff --git a/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..b5a16d4da4075 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,54 @@ +import ShieldIcon from '../index'; + +export default { + title: 'JS Packages/Components/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ 'default', 'success', 'warning', 'error' ], + }, + icon: { + control: { + type: 'select', + }, + options: [ 'success', 'error' ], + }, + fill: { + control: 'color', + }, + outline: { + control: 'boolean', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'success', + outline: false, +}; + +export const Variants = () => { + return ( +
    +
    + + + + +
    +
    + + + + +
    +
    + ); +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index eb90df97ad5fe..4b0f3612012e7 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ShieldIcon } from './components/shield-icon'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/plugins/protect/changelog/refactor-alert-icon b/projects/plugins/protect/changelog/refactor-alert-icon new file mode 100644 index 0000000000000..46b4c247b1b9f --- /dev/null +++ b/projects/plugins/protect/changelog/refactor-alert-icon @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Refactored icon component code. + + diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 758c8c21e0193..5ccf607698084 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,6 +1,9 @@ -import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; +import { + AdminSectionHero as JetpackAdminSectionHero, + H3, + ShieldIcon, +} from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; -import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -12,7 +15,12 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; + Heading: React.FC< { + children: React.ReactNode; + showIcon?: boolean; + variant?: 'default' | 'success' | 'error'; + outline?: boolean; + } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -53,8 +61,10 @@ AdminSectionHero.Heading = ( { { children } { showIcon && ( ) } diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index 5881bcd910045..a414aa9216f5c 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -13,7 +13,7 @@ } .heading-icon { - margin-left: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } @@ -23,4 +23,4 @@ .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px -} \ No newline at end of file +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
    - - - - - - - - - - - - - - - - - - - -
    - ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx deleted file mode 100644 index 47b2ee32d4b51..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import AlertIcon from '../index.jsx'; - -export default { - title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, -}; - -const FooterTemplate = args => ; -export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx deleted file mode 100644 index 3bf7f479f0051..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @param {object} props - Component props. - * @param {string} props.variant - Icon variant. - * @param {string} props.fill - Icon fill color. - * @param {string} props.className - Additional class names. - * @param {number} props.height - Icon height. - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ShieldIcon( { - variant = 'default', - height = 32, - className, - fill, -}: { - variant: - | 'default' - | 'success' - | 'error' - | 'default-outline' - | 'success-outline' - | 'error-outline'; - className?: string; - height?: number; - fill?: string; -} ): JSX.Element { - if ( 'error-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'error' === variant ) { - return ( - - - - - ); - } - - if ( 'success-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'success' === variant ) { - return ( - - - - - ); - } - - if ( 'default-outline' === variant ) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx deleted file mode 100644 index d10365f4b0834..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ShieldIcon from '../index'; - -export default { - title: 'Plugins/Protect/Sheild Icon', - component: ShieldIcon, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], - argTypes: { - variant: { - control: { - type: 'select', - }, - options: [ - 'default', - 'success', - 'error', - 'default-outline', - 'success-outline', - 'error-outline', - ], - }, - fill: { - control: 'color', - }, - }, -}; - -export const Default = args => ; -Default.args = { - variant: 'default', -}; - -export const SuccessVariant = args => ; -SuccessVariant.args = { - variant: 'success', -}; - -export const ErrorVariant = args => ; -ErrorVariant.args = { - variant: 'error', -}; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 4aa517f5f120b..141c51cde284a 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -60,7 +60,7 @@ const HistoryAdminSectionHero: React.FC = () => { __( 'Most recent results', 'jetpack-protect' ) ) } - + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 1c5cc6cac49b9..9e1b9c102a037 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -95,7 +95,7 @@ const ScanAdminSectionHero: React.FC = () => { anchor={ dailyScansPopoverAnchor } /> ) } - + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ From f580acef495fd11cf9e8247df2ef91421723523f Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 5 Dec 2024 10:50:27 -0700 Subject: [PATCH 090/290] Protect: Integrate ThreatsDataViews Component (#40076) --- pnpm-lock.yaml | 3 + .../add-threat-subtitle-and-icon-utils | 4 + projects/js-packages/scan/src/utils/index.ts | 32 +- .../protect/changelog/add-threats-data-views | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 30 +- projects/plugins/protect/src/js/api.ts | 3 +- .../src/js/components/admin-page/index.jsx | 12 +- .../components/admin-page/styles.module.scss | 11 + .../error-admin-section-hero/index.tsx | 4 - .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 14 +- .../src/js/components/navigation/badge.jsx | 101 ------ .../src/js/components/navigation/group.jsx | 51 --- .../src/js/components/navigation/index.jsx | 73 ----- .../src/js/components/navigation/item.jsx | 85 ----- .../src/js/components/navigation/label.jsx | 24 -- .../components/navigation/styles.module.scss | 142 --------- .../navigation/use-menu-navigation.js | 92 ------ .../js/components/paid-accordion/index.jsx | 192 ----------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 202 ------------ .../src/js/components/pricing-table/index.jsx | 4 +- .../components/protect-check-icon/index.tsx | 25 -- .../js/components/scan-navigation/index.jsx | 44 --- .../js/components/threat-fix-header/index.jsx | 7 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 -------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 -------- .../js/components/threats-list/pagination.jsx | 142 --------- .../js/components/threats-list/paid-list.jsx | 253 --------------- .../threats-list/styles.module.scss | 129 -------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/hooks/use-protect-data/index.ts | 173 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../protect/src/js/routes/firewall/index.jsx | 3 +- .../history/history-admin-section-hero.tsx | 84 ----- .../src/js/routes/scan/history/index.jsx | 301 ------------------ .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 37 --- .../protect/src/js/routes/scan/index.jsx | 79 +++-- .../src/js/routes/scan/onboarding-steps.jsx | 61 ++-- .../routes/scan/scan-admin-section-hero.tsx | 138 ++++---- .../src/js/routes/scan/scan-footer.jsx | 143 --------- .../js/routes/scan/scan-results-data-view.tsx | 56 ++++ .../scan/scanning-admin-section-hero.tsx | 25 +- .../src/js/routes/scan/styles.module.scss | 20 +- projects/plugins/protect/webpack.config.js | 18 ++ 53 files changed, 363 insertions(+), 3668 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils create mode 100644 projects/plugins/protect/changelog/add-threats-data-views delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/badge.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/group.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/item.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/label.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/scan-navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/routes/scan/scan-footer.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00711e4c34617..d1779de4f48e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4214,6 +4214,9 @@ importers: specifier: 6.2.2 version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@automattic/babel-plugin-replace-textdomain': + specifier: workspace:* + version: link:../../js-packages/babel-plugin-replace-textdomain '@automattic/jetpack-webpack-config': specifier: workspace:* version: link:../../js-packages/webpack-config diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils new file mode 100644 index 0000000000000..ad8fa81458278 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add utilities for generating threat subtitle and icons diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 945cd0ecb7fa8..9e2e75bcd4d91 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => { return null; }; +export const getThreatIcon = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return 'wordpress-alt'; + case 'plugin': + return 'plugins'; + case 'theme': + return 'appearance'; + case 'file': + return 'media-code'; + default: + return 'shield-alt'; + } +}; + +export const getThreatSubtitle = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); + case 'plugin': + return __( 'Vulnerable Plugin', 'jetpack-scan' ); + case 'theme': + return __( 'Vulnerable Theme', 'jetpack-scan' ); + case 'file': + return __( 'File Threat', 'jetpack-scan' ); + default: + return __( 'Threat', 'jetpack-scan' ); + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); @@ -123,7 +153,7 @@ export const getFixerDescription = ( threat: Threat ) => { } break; case 'update': - if ( threat.fixedIn && threat.extension.name ) { + if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ __( 'Update %1$s to version %2$s', 'jetpack-scan' ), diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views new file mode 100644 index 0000000000000..e15bd6a461a71 --- /dev/null +++ b/projects/plugins/protect/changelog/add-threats-data-views @@ -0,0 +1,5 @@ +Significance: minor +Type: changed + +Added DataViews component for viewing scan results. + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index 9d11e44dc60af..8708e2c101c9f 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -49,6 +49,7 @@ "react-router-dom": "6.2.2" }, "devDependencies": { + "@automattic/babel-plugin-replace-textdomain": "workspace:*", "@automattic/jetpack-webpack-config": "workspace:*", "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index bd034c375caf9..8ea1dec7156e7 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -207,43 +207,19 @@ public static function fetch_from_api() { * Normalize API Data * Formats the payload from the Scan API into an instance of History_Model. * - * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility. - * * @param object $scan_data The data returned by the scan API. * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..97d11fd5c0f2b 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,6 +1,7 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { WafStatus } from './types/waf'; const API = { getWaf: (): Promise< WafStatus > => diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 2d023560517f3..68f9359a9bd81 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useNotices from '../../hooks/use-notices'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; import ScanButton from '../scan-button'; @@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => { const { isRegistered } = useConnection(); const { isSeen: wafSeen } = useWafData(); const navigate = useNavigate(); - const { - counts: { - current: { threats: numThreats }, - }, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); const location = useLocation(); const { hasPlan } = usePlan(); @@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => { link="/scan" label={ - { numThreats > 0 + { status.threats.length > 0 ? sprintf( // translators: %d is the number of threats found. __( 'Scan (%d)', 'jetpack-protect' ), - numThreats + status.threats.length ) : __( 'Scan', 'jetpack-protect' ) } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adc0cee561ba5..da2e9510cd7d9 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -5,10 +5,21 @@ .header { display: flex; justify-content: space-between; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px + align-items: center; + + @media ( min-width: 600px ) { + flex-direction: row; + } &__scan_buttons { display: flex; gap: calc( var( --spacing-base ) * 3 ); // 24px + + @media ( min-width: 600px ) { + flex-direction: row; + } } } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 5214531dcf362..1a9bc87387fa9 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; -import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; interface ErrorAdminSectionHeroProps { @@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { { displayErrorMessage } -
    - -
    } preserveSecondaryOnMobile={ false } diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
    - +
    diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx index 7e8113b6f38ab..0788eb8bd7a41 100644 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx @@ -1,16 +1,18 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx deleted file mode 100644 index 93ebecf7235ef..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/badge.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, check, info } from '@wordpress/icons'; -import PropTypes from 'prop-types'; -import React, { useState, useCallback, useMemo } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import styles from './styles.module.scss'; - -/** - * Gets the Badge element - * - * @param {number} count - The number of threats found for this item. - * @param {boolean} checked - Whether this item was checked for threats yet. - * @return {object} The badge element - */ -const getBadgeElement = ( count, checked ) => { - if ( ! checked ) { - return { - popoverText: __( - 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', - 'jetpack-protect' - ), - badgeElement: ( - - ), - }; - } - - if ( count === 0 ) { - return { - popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ), - badgeElement: ( - - ), - }; - } - - return { - popoverText: null, - badgeElement: ( - - { count } - - ), - }; -}; - -const ItemBadge = ( { count, checked } ) => { - const { data: status } = useScanStatusQuery(); - - const { popoverText, badgeElement } = getBadgeElement( count, checked ); - const [ showPopover, setShowPopover ] = useState( false ); - - const inProgress = useMemo( () => isScanInProgress( status ), [ status ] ); - - const handleEnter = useCallback( () => { - if ( inProgress ) { - return; - } - - setShowPopover( true ); - }, [ inProgress ] ); - - const handleOut = useCallback( () => { - setShowPopover( false ); - }, [] ); - - return ( -
    - { ! inProgress ? badgeElement : } - { showPopover && ( - - - { popoverText } - - - ) } -
    - ); -}; - -ItemBadge.propTypes = { - /* The number of threats found for this item */ - count: PropTypes.number, - /* Whether this item was checked for threats yet */ - checked: PropTypes.bool, -}; - -export default ItemBadge; diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx deleted file mode 100644 index 9352ae5c63d67..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/group.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useState, useCallback, useContext } from 'react'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const MAX_ITEMS = 8; - -const NavigationGroup = ( { icon, label, children } ) => { - const [ collapsed, setCollapsed ] = useState( true ); - const { mode } = useContext( NavigationContext ); - const needsTruncate = - Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list'; - const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children; - const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0; - - const handleCollapsedToggle = useCallback( () => { - setCollapsed( current => ! current ); - }, [] ); - - return ( -
  • - - { label } - -
    -
      { content }
    - { needsTruncate && ( -
    - -
    - ) } -
    -
  • - ); -}; - -export default NavigationGroup; diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx deleted file mode 100644 index bd30dfbdad964..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import React, { useState, useRef, useCallback } from 'react'; -import NavigationGroup from './group'; -import NavigationItem from './item'; -import styles from './styles.module.scss'; -import useMenuNavigation, { NavigationContext } from './use-menu-navigation'; - -const NavigationList = ( { children } ) => ( -
      - { children } -
    -); - -const NavigationDropdown = ( { children, data } ) => { - const ref = useRef( undefined ); - const [ listOpen, setListOpen ] = useState( false ); - const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? { - label: __( 'See all results', 'jetpack-protect' ), - }; - const { label, icon } = item; - - const handleOpen = useCallback( () => { - setListOpen( open => ! open ); - }, [] ); - - return ( - - ); -}; - -const getNavigationComponent = mode => { - switch ( mode ) { - case 'list': - return NavigationList; - case 'dropdown': - return NavigationDropdown; - default: - return NavigationList; - } -}; - -const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => { - const data = useMenuNavigation( { selected, onSelect } ); - const Component = getNavigationComponent( mode ); - - return ( - - { children } - - ); -}; - -export default Navigation; -export { NavigationItem, NavigationGroup }; diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx deleted file mode 100644 index d902625c3997d..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/item.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import clsx from 'clsx'; -import React, { useContext, useEffect, useCallback } from 'react'; -import ItemBadge from './badge'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const NavigationItem = ( { - id, - label, - icon, - badge, - disabled, - onClick, - onKeyDown, - onFocus, - checked, -} ) => { - const context = useContext( NavigationContext ); - - const selected = context?.selectedItem === id; - const registerItem = context?.registerItem; - const registerRef = context?.registerRef; - const handleClickItem = context?.handleClickItem; - const handleKeyDownItem = context?.handleKeyDownItem; - const handleFocusItem = context?.handleFocusItem; - - const wrapperClassName = clsx( styles[ 'navigation-item' ], { - [ styles.clickable ]: ! disabled, - [ styles.selected ]: selected, - } ); - - const handleClick = useCallback( - evt => { - onClick?.( evt ); - handleClickItem?.( id ); - }, - [ handleClickItem, id, onClick ] - ); - - const handleKeyDown = useCallback( - evt => { - onKeyDown?.( evt ); - handleKeyDownItem?.( evt ); - }, - [ handleKeyDownItem, onKeyDown ] - ); - - const handleRef = useCallback( - ref => { - registerRef( ref, id ); - }, - [ registerRef, id ] - ); - - const handleFocus = useCallback( - evt => { - onFocus?.( evt ); - handleFocusItem?.( id ); - }, - [ handleFocusItem, id, onFocus ] - ); - - useEffect( () => { - registerItem( { id, disabled, label, icon } ); - // eslint-disable-next-line - }, [] ); - - return ( -
  • - { label } - -
  • - ); -}; - -export default NavigationItem; diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx deleted file mode 100644 index 8f075caae020a..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/label.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon } from '@wordpress/icons'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './styles.module.scss'; - -const ItemLabel = ( { icon, children, className } ) => { - return ( - - { icon && } - { children } - - ); -}; - -ItemLabel.propTypes = { - /* An icon that will be rendered before text */ - icon: PropTypes.node, - /* Label text that will be rendered */ - children: PropTypes.node.isRequired, -}; - -export default ItemLabel; diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss deleted file mode 100644 index df9e3ef1f8a25..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss +++ /dev/null @@ -1,142 +0,0 @@ -.navigation { - background-color: var( --jp-white ); - box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); - border-radius: var( --jp-border-radius ); - margin: 0; -} - -.navigation-item { - display: flex; - padding: calc( var( --spacing-base ) * 2 ); // 16px; - align-items: center; - justify-content: space-between; - margin: 0; - text-align: left; - - // Clickable State - &.clickable { - cursor: pointer; - outline-color: var( --jp-black ); - - // Focus/Hover State - &:hover:not(.selected), - &:focus:not(.selected) { - background-color: var( --jp-gray-0 ); - } - } - - // Selected State - &.selected { - background-color: var( --jp-black ); - - & .navigation-item-label { - color: var( --jp-white ) - } - - & .navigation-item-icon { - fill: var( --jp-white ); - } - - & .navigation-item-badge { - border: 1px solid var( --jp-red ); - background-color: var( --jp-red ); - color: var( --jp-white ); - } - } - - // CHILDRENS - - // .navigation-item-label - &-label { - display: flex; - align-items: center; - padding-right: var( --spacing-base ); // 8px - overflow-x: hidden; - } - - // .navigation-item-label-content - &-label-text { - display: block; - overflow-x: hidden; - text-overflow: ellipsis; - } - - // .navigation-item-icon - &-icon { - margin-right: calc( var( --spacing-base ) * 2); // 16px - } - - // .navigation-item-badge - &-badge { - border: 1px solid var( --jp-red-60 ); - color: var( --jp-red-60 ); - border-radius: 50%; - padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px - min-width: 30px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - &-check-badge { - fill: var( --jp-green-50 ); - } - - &-info-badge { - fill: var( --jp-gray-20 ); - } -} - -.navigation-group { - --icon-size: 28px; - --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px - --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px - - list-style: none; - - &-label { - padding: calc( var( --spacing-base ) * 2 ); // 16px - } - - &-content { - padding: 0; - } - - &-list { - margin-left: var( --left-spacing ) ; - } - - &-truncate { - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - justify-content: flex-start; - } -} - -.popover-text { - width: 250px; - padding: calc( var( --spacing-base ) * 2 ); // 16px -} - -.navigation-dropdown { - &-button { - display: flex; - border: 1px solid var( --jp-gray-10 ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - background-color: var( --jp-white ); - justify-content: space-between; - align-items: center; - width: 100%; - } - - &-label { - display: flex; - justify-content: flex-start; - } - - &-icon { - margin-right: var( --spacing-base ); // 8px - } -} diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js deleted file mode 100644 index 2972dac06b572..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -export const NavigationContext = React.createContext(); - -const useMenuNavigation = ( { selected, onSelect } ) => { - const [ items, setItems ] = useState( [] ); - const [ refs, setRef ] = useState( [] ); - const [ focusedItem, setFocusedItem ] = useState(); - - const handleClickItem = id => { - onSelect( id ); - }; - - const handleFocusItem = id => { - setFocusedItem( id ); - }; - - const getPrevItem = ( current, last ) => { - const startMinusOne = current - 1; - const prevIndex = startMinusOne < 0 ? last : startMinusOne; - const prevItem = items[ prevIndex ]; - return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem; - }; - - const getNextItem = ( current, last ) => { - const startPlusOne = current + 1; - const nextIndex = startPlusOne > last ? 0 : startPlusOne; - const nextItem = items[ nextIndex ]; - return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem; - }; - - const handleKeyDownItem = input => { - const code = input?.code; - const current = items.findIndex( item => item?.id === selected ); - const lastIndex = items.length - 1; - - let nextId; - - if ( code === 'ArrowUp' ) { - const prevItem = getPrevItem( current, lastIndex ); - nextId = prevItem?.id; - } else if ( code === 'ArrowDown' ) { - const nextItem = getNextItem( current, lastIndex ); - nextId = nextItem?.id; - } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) { - nextId = focusedItem; - } - - if ( nextId ) { - const element = refs[ nextId ]; - element?.focus(); - onSelect( nextId ); - } - }; - - const registerRef = ( ref, id ) => { - setRef( allRefs => { - if ( ! allRefs[ id ] && ref ) { - return { ...allRefs, [ id ]: ref }; - } - return allRefs; - } ); - }; - - const registerItem = data => { - setItems( allItems => { - const newItems = [ ...allItems ]; - const id = data?.id; - const currentIdx = newItems.findIndex( item => item?.id === id ); - - if ( currentIdx >= 0 ) { - newItems[ currentIdx ] = data; - } else { - newItems.push( data ); - } - - return newItems; - } ); - }; - - return { - selectedItem: selected, - handleClickItem, - handleKeyDownItem, - handleFocusItem, - registerRef, - registerItem, - items, - }; -}; - -export default useMenuNavigation; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index c733ff1f0a08c..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - IconTooltip, - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
    - { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
    -); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - - - { createInterpolateElement( - __( - 'The fixer is taking longer than expected. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - - - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 8304942b206d4..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,202 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -.icon-tooltip { - max-height: 20px; - margin-left: calc( var( --spacing-base ) / 2 ); // 4px - - &__icon { - color: var( --jp-red ); - } - - &__content { - color: var( --jp-gray-70 ); - font-weight: 400; - line-height: 24px; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx deleted file mode 100644 index d1100d8ce6d5e..0000000000000 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ProtectCheck(): JSX.Element { - return ( - - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx deleted file mode 100644 index e626b6af066c7..0000000000000 --- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import usePlan from '../../hooks/use-plan'; -import ButtonGroup from '../button-group'; - -/** - * Navigation for scan sections. - * - * @return {React.Element} The React Component. - */ -export default function ScanNavigation() { - const navigate = useNavigate(); - const location = useLocation(); - const { hasPlan } = usePlan(); - - const viewingScanPage = location.pathname === '/scan'; - const viewingHistoryPage = location.pathname.includes( '/scan/history' ); - const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] ); - const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] ); - - if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) { - return null; - } - - return ( - <> - - - { __( 'Scanner', 'jetpack-protect' ) } - - - { __( 'History', 'jetpack-protect' ) } - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..45a8524e60b59 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,6 +1,7 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; import styles from './styles.module.scss'; @@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } ) return ( <>
    - +
    - { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
    - ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
    - - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
    - ) } - { fixedIn && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
    - ) } - { ! description &&
    { learnMoreButton }
    } -
    - ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
    - -
    - { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
    - { getTitle() } - { hasPlan && ( -
    - { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
    - ) } -
    - { hasPlan ? ( - <> -
    - -
    - - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
    -
    - { ! scanning && ( -
    - ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
    - - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
    - ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
    { filename }
    - - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
    - ) } - { ! description &&
    { learnMoreButton }
    } - { [ 'ignored', 'current' ].includes( status ) && ( -
    - { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
    - ) } -
    - ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
    - { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
    - ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index 2338d306e6780..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx index 0a3c26782790a..9604ef495811f 100644 --- a/projects/plugins/protect/src/js/routes/firewall/index.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx @@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; -import ScanFooter from '../scan/scan-footer'; import FirewallAdminSectionHero from './firewall-admin-section-hero'; import FirewallFooter from './firewall-footer'; import styles from './styles.module.scss'; @@ -597,7 +596,7 @@ const FirewallPage = () => {
    - { wafSupported ? : } + { wafSupported && } ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 141c51cde284a..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previous %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previous threats', 'jetpack-protect' ) } - - - - { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } - - - - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
    -
    - { getTitle() } -
    - -
    -
    - -
    - ) : ( - <> -
    -
    - -
    -
    -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
    - - ) } - -
    - -
    -
    - ) } - -
    - ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index d30f3e0ac3344..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..c56ae3c747f3e 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,16 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import OnboardingPopover from '../../components/onboarding-popover'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; -import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; +import styles from './styles.module.scss'; /** * Scan Page @@ -19,23 +21,41 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); + const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: [ 'current' ], + operator: 'isAny', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,16 +69,33 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } - + + + +
    + +
    + { !! status && ! isScanInProgress( status ) && ( + + ) } + { !! status && ! isScanInProgress( status ) && hasPlan && ( + + ) } + +
    +
    ); diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx index 0e85aa56d9289..c29af26bcb409 100644 --- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx +++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx @@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan'; const { siteSuffix } = window.jetpackProtectInitialState; -const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' ); -const scanResultsDescription = ( - - { __( - 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files', - 'jetpack-protect' - ) } - -); const UpgradeButton = props => { const { upgradePlan } = usePlan(); const { recordEvent } = useAnalyticsTracks(); @@ -27,11 +18,6 @@ const UpgradeButton = props => { }; export default [ - { - id: 'free-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, - }, { id: 'free-daily-scans', title: __( 'Daily automated scans', 'jetpack-protect' ), @@ -49,10 +35,41 @@ export default [ ), }, + { + id: 'paid-daily-and-manual-scans', + title: __( 'Daily & manual scanning', 'jetpack-protect' ), + description: ( + + { __( + 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', + 'jetpack-protect' + ) } + + ), + }, + { + id: 'free-scan-results', + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.', + 'jetpack-protect' + ) } + + ), + }, { id: 'paid-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.', + 'jetpack-protect' + ) } + + ), }, { id: 'paid-fix-all-threats', @@ -97,16 +114,4 @@ export default [ ), }, - { - id: 'paid-daily-and-manual-scans', - title: __( 'Daily & manual scanning', 'jetpack-protect' ), - description: ( - - { __( - 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', - 'jetpack-protect' - ) } - - ), - }, ]; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 9e1b9c102a037..db76bac1b15b0 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,33 +1,41 @@ import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); - const { hasPlan } = usePlan(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); - const { list } = useThreatsList(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const { setModal } = useModal(); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); + + const { globalStats } = useWafData(); + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); + + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); @@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => { // List of fixable threats that do not have a fix in progress const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; return ( threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) ); } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } const handleShowAutoFixersClick = threatList => { @@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => { ) : __( 'Most recent results', 'jetpack-protect' ) } - { ! hasPlan && ( - - ) } + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( @@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => { ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) ) - : sprintf( - /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No active %s', 'jetpack-protect' ), - hasPlan - ? __( 'threats', 'jetpack-protect' ) - : __( - 'vulnerabilities', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) - ) } + : __( "Don't worry about a thing", 'jetpack-protect' ) } <> - - { __( - 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', - 'jetpack-protect' - ) } - - { fixableList.length > 0 && ( + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } + + ) : ( <> - - { ! scanning && ( - -
    - -
    } secondary={ } diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 908e34f6e71d7..163fd23248aaa 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,12 +1,14 @@ -.subheading-text { - white-space: nowrap; +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.product-section, .info-section { - margin-top: calc( var( --spacing-base ) * 7 ); // 56px - margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px -} +.scan-results-container { + padding-left: 0; + padding-right: 0; + overflow: hidden; -.auto-fixers { - margin-top: calc( var( --spacing-base ) * 4 ); // 32px -} \ No newline at end of file + > * { + margin-left: calc( var( --spacing-base ) * -3 ); // -24px + margin-right: calc( var( --spacing-base ) * -3 ); // -24px + } +} diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..0c65dfec146a7 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -33,6 +33,24 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + /** + * Transpile @wordpress/dataviews in node_modules too. + * + * @see https://github.com/Automattic/jetpack/issues/39907 + */ + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress/dataviews/' ], + babelOpts: { + configFile: false, + plugins: [ + [ + require.resolve( '@automattic/babel-plugin-replace-textdomain' ), + { textdomain: 'jetpack-protect' }, + ], + ], + }, + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From c384d6933e305baa4aef5874174e69179f635563 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:22:55 -0800 Subject: [PATCH 091/290] Components: Add ScanReport (#40419) --- .../changelog/components-add-scan-report | 4 + .../components/scan-report/constants.ts | 30 +++ .../components/scan-report/index.tsx | 197 ++++++++++++++++++ .../scan-report/stories/index.stories.tsx | 72 +++++++ .../components/scan-report/styles.module.scss | 21 ++ .../components/shield-icon/index.tsx | 14 +- .../shield-icon/stories/index.stories.tsx | 6 +- projects/js-packages/components/index.ts | 1 + .../scan/changelog/components-add-scan-report | 4 + .../js-packages/scan/src/types/threats.ts | 24 ++- 10 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 projects/js-packages/components/changelog/components-add-scan-report create mode 100644 projects/js-packages/components/components/scan-report/constants.ts create mode 100644 projects/js-packages/components/components/scan-report/index.tsx create mode 100644 projects/js-packages/components/components/scan-report/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/scan-report/styles.module.scss create mode 100644 projects/js-packages/scan/changelog/components-add-scan-report diff --git a/projects/js-packages/components/changelog/components-add-scan-report b/projects/js-packages/components/changelog/components-add-scan-report new file mode 100644 index 0000000000000..ba0fbd4cce025 --- /dev/null +++ b/projects/js-packages/components/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport component diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts new file mode 100644 index 0000000000000..436eed91c5701 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -0,0 +1,30 @@ +import { __ } from '@wordpress/i18n'; +import { + code as fileIcon, + color as themeIcon, + plugins as pluginIcon, + shield as shieldIcon, + wordpress as coreIcon, +} from '@wordpress/icons'; + +export const TYPES = [ + { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, + { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, + { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, + { value: 'files', label: __( 'Files', 'jetpack-components' ) }, +]; + +export const ICONS = { + plugins: pluginIcon, + themes: themeIcon, + core: coreIcon, + files: fileIcon, + default: shieldIcon, +}; + +export const FIELD_ICON = 'icon'; +export const FIELD_TYPE = 'type'; +export const FIELD_NAME = 'name'; +export const FIELD_STATUS = 'status'; +export const FIELD_UPDATE = 'update'; +export const FIELD_VERSION = 'version'; diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx new file mode 100644 index 0000000000000..14795376f7d95 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -0,0 +1,197 @@ +import { type ScanReportExtension } from '@automattic/jetpack-scan'; +import { Tooltip } from '@wordpress/components'; +import { + type SupportedLayouts, + type View, + type Field, + DataViews, + filterSortAndPaginate, +} from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; +import { Icon } from '@wordpress/icons'; +import { useCallback, useMemo, useState } from 'react'; +import ShieldIcon from '../shield-icon'; +import { + FIELD_NAME, + FIELD_VERSION, + FIELD_ICON, + FIELD_STATUS, + FIELD_TYPE, + TYPES, + ICONS, +} from './constants'; +import styles from './styles.module.scss'; + +/** + * DataViews component for displaying a scan report. + * + * @param {object} props - Component props. + * @param {Array} props.data - Scan report data. + * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * + * @return {JSX.Element} The ScanReport component. + */ +export default function ScanReport( { data, onChangeSelection } ): JSX.Element { + const baseView = { + search: '', + filters: [], + page: 1, + perPage: 20, + }; + + /** + * DataView default layouts. + * + * This property provides layout information about the view types that are active. If empty, enables all layout types (see “Layout Types”) with empty layout data. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view + */ + const defaultLayouts: SupportedLayouts = { + table: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_TYPE, FIELD_NAME, FIELD_VERSION ], + layout: { + primaryField: FIELD_STATUS, + }, + }, + list: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_VERSION ], + layout: { + primaryField: FIELD_NAME, + mediaField: FIELD_ICON, + }, + }, + }; + + /** + * DataView view object - configures how the dataset is visible to the user. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object + */ + const [ view, setView ] = useState< View >( { + type: 'table', + ...defaultLayouts.table, + } ); + + /** + * DataView fields - describes the visible items for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object + */ + const fields = useMemo( () => { + const iconHeight = 20; + const result: Field< ScanReportExtension >[] = [ + { + id: FIELD_STATUS, + label: __( 'Status', 'jetpack-components' ), + render( { item }: { item: ScanReportExtension } ) { + let variant: 'info' | 'warning' | 'success' = 'info'; + let text = __( + 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', + 'jetpack-components' + ); + + if ( item.checked ) { + if ( item.threats.length > 0 ) { + variant = 'warning'; + text = __( 'Threat detected.', 'jetpack-components' ); + } else { + variant = 'success'; + text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + } + } + + return ( + +
    + +
    +
    + ); + }, + }, + { + id: FIELD_TYPE, + label: __( 'Type', 'jetpack-components' ), + elements: TYPES, + }, + { + id: FIELD_NAME, + label: __( 'Name', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.name ? item.name : ''; + }, + }, + { + id: FIELD_VERSION, + label: __( 'Version', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.version ? item.version : ''; + }, + }, + ...( view.type === 'list' + ? [ + { + id: FIELD_ICON, + label: __( 'Icon', 'jetpack-components' ), + enableSorting: false, + enableHiding: false, + getValue( { item }: { item: ScanReportExtension } ) { + return ICONS[ item.type ] || ''; + }, + render( { item }: { item: ScanReportExtension } ) { + return ( +
    + +
    + ); + }, + }, + ] + : [] ), + ]; + + return result; + }, [ view ] ); + + /** + * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. + * + * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts + */ + const { data: processedData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, fields ); + }, [ data, view, fields ] ); + + /** + * Callback function to update the view state. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function + */ + const onChangeView = useCallback( ( newView: View ) => { + setView( newView ); + }, [] ); + + /** + * DataView getItemId function - returns the unique ID for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function + */ + const getItemId = useCallback( ( item: ScanReportExtension ) => item.id.toString(), [] ); + + return ( + + ); +} diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx new file mode 100644 index 0000000000000..63926908850de --- /dev/null +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -0,0 +1,72 @@ +import ScanReport from '..'; + +export default { + title: 'JS Packages/Components/Scan Report', + component: ScanReport, + parameters: { + backgrounds: { + default: 'light', + values: [ { name: 'light', value: 'white' } ], + }, + }, + decorators: [ + Story => ( +
    + +
    + ), + ], +}; + +export const Default = args => ; +Default.args = { + data: [ + { + id: 1, + name: 'WordPress', + slug: null, + version: '6.7.1', + threats: [], + checked: true, + type: 'core', + }, + { + id: 2, + name: 'Jetpack', + slug: 'jetpack/jetpack.php', + version: '14.1-a.7', + threats: [], + checked: false, + type: 'plugins', + }, + { + id: 3, + name: 'Twenty Fifteen', + slug: 'twentyfifteen', + version: '1.1', + threats: [ + { + id: 198352527, + signature: 'Vulnerable.WP.Extension', + description: 'Vulnerable WordPress extension', + severity: 3, + }, + ], + checked: true, + type: 'themes', + }, + { + id: 4, + threats: [ + { + id: 198352406, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, + ], + checked: true, + type: 'files', + }, + ], +}; diff --git a/projects/js-packages/components/components/scan-report/styles.module.scss b/projects/js-packages/components/components/scan-report/styles.module.scss new file mode 100644 index 0000000000000..d313d4cb8898a --- /dev/null +++ b/projects/js-packages/components/components/scan-report/styles.module.scss @@ -0,0 +1,21 @@ +@import '@wordpress/dataviews/build-style/style.css'; + +.threat__media { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #EDFFEE; + border-color: #EDFFEE; + + svg { + fill: var( --jp-black ); + } +} + +.tooltip { + max-width: 240px; + border-radius: 4px; + text-align: left; +} \ No newline at end of file diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx index fee9f4d70c463..b07b943b5e7fa 100644 --- a/projects/js-packages/components/components/shield-icon/index.tsx +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; const COLORS = { - error: '#D63638', - warning: '#F0B849', - success: '#069E08', default: '#1d2327', + info: '#A7AAAD', + success: '#069E08', + warning: '#F0B849', + error: '#D63638', }; /** @@ -32,11 +33,11 @@ export default function ShieldIcon( { }: { className?: string; contrast?: string; - fill?: 'default' | 'success' | 'warning' | 'error' | string; + fill?: 'default' | 'info' | 'success' | 'warning' | 'error' | string; height?: number; icon?: 'success' | 'error'; outline?: boolean; - variant: 'default' | 'success' | 'warning' | 'error'; + variant: 'default' | 'info' | 'success' | 'warning' | 'error'; } ): JSX.Element { const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; const iconFill = outline ? shieldFill : contrast; @@ -60,6 +61,9 @@ export default function ShieldIcon( { } fill={ shieldFill } /> + { 'info' === iconVariant && ( + + ) } { 'success' === iconVariant && ( {
    +
    + diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index 4b0f3612012e7..6df50ee7fdb61 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -48,6 +48,7 @@ export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; export { default as ShieldIcon } from './components/shield-icon'; +export { default as ScanReport } from './components/scan-report'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/js-packages/scan/changelog/components-add-scan-report b/projects/js-packages/scan/changelog/components-add-scan-report new file mode 100644 index 0000000000000..eeb9c55de4a28 --- /dev/null +++ b/projects/js-packages/scan/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates/adds scan types diff --git a/projects/js-packages/scan/src/types/threats.ts b/projects/js-packages/scan/src/types/threats.ts index 72428c209ee49..22f6b06477163 100644 --- a/projects/js-packages/scan/src/types/threats.ts +++ b/projects/js-packages/scan/src/types/threats.ts @@ -4,6 +4,23 @@ export type ThreatStatus = 'fixed' | 'ignored' | 'current'; export type ThreatFixType = 'replace' | 'delete' | 'update' | string; +export type ScanReportExtension = { + id: number; + checked: boolean; + slug?: string; + name?: string; + version?: string; + threats: Threat[]; + type: 'plugins' | 'themes' | 'core' | 'files'; +}; + +export type Extension = { + slug: string; + name: string; + version: string; + type: 'plugins' | 'themes' | 'core'; +}; + export type Threat = { /** The threat's unique ID. */ id: string | number; @@ -57,10 +74,5 @@ export type Threat = { diff?: string; /** The affected extension. */ - extension?: { - slug: string; - name: string; - version: string; - type: 'plugins' | 'themes' | 'core'; - }; + extension?: Extension; }; From 68569f2c94c454c90d7369da8b8e07780fbff3d4 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 6 Dec 2024 12:05:11 -0700 Subject: [PATCH 092/290] Fix type errors Protect: add HMR support Revert "Protect: add HMR support" This reverts commit 06497a05bb050c86e097b36038c8742af427388d. --- projects/js-packages/scan/src/utils/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 9e2e75bcd4d91..30a96cbd132d5 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,9 +21,9 @@ export const getThreatIcon = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return 'wordpress-alt'; - case 'plugin': + case 'plugins': return 'plugins'; - case 'theme': + case 'themes': return 'appearance'; case 'file': return 'media-code'; @@ -36,9 +36,9 @@ export const getThreatSubtitle = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); - case 'plugin': + case 'plugins': return __( 'Vulnerable Plugin', 'jetpack-scan' ); - case 'theme': + case 'themes': return __( 'Vulnerable Theme', 'jetpack-scan' ); case 'file': return __( 'File Threat', 'jetpack-scan' ); From 91ee20a8f074545362714bcfd104b4ef10a4080d Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 9 Dec 2024 14:38:56 -0700 Subject: [PATCH 093/290] Protect: Refactor AdminSectionHero (#40516) --- .../components/admin-section-hero/index.tsx | 96 +++++----- .../stories/index.stories.jsx | 16 +- .../admin-section-hero/styles.module.scss | 44 +++-- .../error-admin-section-hero/index.tsx | 30 ++- .../styles.module.scss | 6 +- .../firewall/firewall-admin-section-hero.tsx | 27 ++- .../js/routes/firewall/firewall-statcards.jsx | 2 +- .../src/js/routes/firewall/styles.module.scss | 49 +++-- .../routes/scan/scan-admin-section-hero.tsx | 177 +++++++++--------- .../scan/scanning-admin-section-hero.tsx | 70 ++++--- .../src/js/routes/scan/styles.module.scss | 10 + 11 files changed, 275 insertions(+), 252 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ccf607698084..7638936db5139 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -2,67 +2,73 @@ import { AdminSectionHero as JetpackAdminSectionHero, H3, ShieldIcon, + Container, + Col, } from '@automattic/jetpack-components'; -import SeventyFiveLayout from '../seventy-five-layout'; +import clsx from 'clsx'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; -interface AdminSectionHeroProps { - main: React.ReactNode; - secondary?: React.ReactNode; - preserveSecondaryOnMobile?: boolean; - spacing?: number; -} - -interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { - children: React.ReactNode; - showIcon?: boolean; - variant?: 'default' | 'success' | 'error'; - outline?: boolean; - } >; - Subheading: React.FC< { children: React.ReactNode } >; -} - -const AdminSectionHero: AdminSectionHeroComponent = ( { - main, - secondary, - preserveSecondaryOnMobile = true, - spacing = 7, -} ) => { +const AdminSectionHero = ( { + children, + ...props +}: React.ComponentProps< typeof JetpackAdminSectionHero > ) => { return ( - + - + + +
    { children }
    + +
    ); }; -AdminSectionHero.Heading = ( { +AdminSectionHero.Main = ( { children, - variant = 'default', - showIcon = false, + className, + ...props }: { children: React.ReactNode; - variant?: 'default' | 'success' | 'error'; - showIcon?: boolean; + className?: string; + [ key: string ]: unknown; +} ) => { + return ( +
    + { children } +
    + ); +}; + +AdminSectionHero.Aside = ( { + children, + className, + ...props +}: React.ComponentProps< 'div' > & { + className?: string; } ) => { return ( -

    +
    { children } - { showIcon && ( +
    + ); +}; + +AdminSectionHero.Heading = ( { + children, + icon, + ...props +}: React.ComponentProps< typeof H3 > & { + icon?: 'default' | 'success' | 'error'; +} ) => { + return ( +

    + { children } + { !! icon && ( { - return
    { children }
    ; -}; - export default AdminSectionHero; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index ca2dfda7fc98e..59ed9086d6317 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -9,16 +9,16 @@ export default { export const Default = args => ; Default.args = { - main: ( + children: ( <> - - - { 'No threats found' } - - + + + { 'No threats found' } { 'Most recent results' } - + + + + ), - secondary: , }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index a414aa9216f5c..74cfe29aaaded 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -1,15 +1,39 @@ -.header-main { +.admin-section-hero { display: flex; flex-direction: column; - justify-content: center; - align-items: flex-start; + gap: calc( var( --spacing-base ) * 6 ); // 48px + + max-width: var(--max-container-width); + padding: calc( var( --spacing-base ) * 6 ) 0; // 48px 0 + margin: 0 auto; + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media ( min-width: 1100px ) { + flex-direction: row; + align-items: center; + gap: calc( var( --spacing-base ) * 3 ); // 24px + } } -.header-secondary { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; +.admin-section-hero__main { + flex: 2; +} + +.admin-section-hero__aside { + flex: 1; + flex-shrink: 0; + + @media ( min-width: 1200px ) { + display: flex; + justify-content: flex-end; + } } .heading-icon { @@ -17,10 +41,6 @@ margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } -.subheading { - width: fit-content; -} - .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 1a9bc87387fa9..536d8f50de7d1 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -1,6 +1,5 @@ -import { Text } from '@automattic/jetpack-components'; +import { ShieldIcon, Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; import styles from './styles.module.scss'; @@ -19,22 +18,17 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { displayErrorMessage += ' ' + __( 'Try again in a few minutes.', 'jetpack-protect' ); return ( - - -
    - - { __( 'An error occurred', 'jetpack-protect' ) } -
    -
    - - { displayErrorMessage } - - - } - preserveSecondaryOnMobile={ false } - /> + + + +
    + { __( 'An error occurred', 'jetpack-protect' ) } + +
    +
    + { displayErrorMessage } +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss index 6f0750abd02f8..1c89377d4b4b5 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss @@ -4,11 +4,7 @@ } .warning { - width: 54px; - height: 54px; - fill: var( --jp-red ); - margin-left: -8px; - margin-right: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px } .scan-navigation { diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 3f70a75509b76..837f649c67f16 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -5,6 +5,7 @@ import AdminSectionHero from '../../components/admin-section-hero'; import useWafData from '../../hooks/use-waf-data'; import FirewallStatCards from './firewall-statcards'; import FirewallSubheading from './firewall-subheading'; +import styles from './styles.module.scss'; const FirewallAdminSectionHero = () => { const { @@ -84,16 +85,22 @@ const FirewallAdminSectionHero = () => { }, [ status ] ); return ( - - - { heading } - { subheading } - - } - secondary={ wafSupported && } - /> + + + + { heading } + { subheading } + + { wafSupported && ( + + + + ) } + ); }; diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 15c80df763c18..1eebd67cb60d7 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -93,7 +93,7 @@ const FirewallStatCards = () => { ); return ( -
    +
    diff --git a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss index c0350ff100e6b..06c41c479d172 100644 --- a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss @@ -3,6 +3,10 @@ max-width: calc( 744px + ( var( --spacing-base ) * 6 ) ); // 744px + 48px (desired inner width + horizontal padding) } +.status { + margin-bottom: calc( var( --spacing-base ) * 2 ); // 16px +} + .toggle-section { display: flex; @@ -145,14 +149,10 @@ align-items: center; } -.stat-card-wrapper { +.stat-cards-wrapper { display: flex; - margin-left: auto; - flex-wrap: wrap; - - >:first-child { - margin-right: calc( var( --spacing-base ) * 3 ); // 24px - } + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 3 ); // 24px .disabled { opacity: 0.5; @@ -171,27 +171,6 @@ white-space: nowrap; } -@media ( max-width: 1115px ) { - .stat-card-wrapper { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px - } -} - -@media ( max-width: 599px ) { - .stat-card-wrapper { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px - - >:first-child { - margin-right: 0; - margin-bottom: var( --spacing-base ); // 8px - } - } - - .stat-card-icon { - margin-bottom: 0; - } -} - .footer-checkbox { display: flex; align-items: center; @@ -231,7 +210,21 @@ } } +@media ( max-width: 1200px ) { + .stat-cards-wrapper { + justify-content: flex-start; + } +} + @media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } .share-data-section { margin-top: 0; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index db76bac1b15b0..4257c585351eb 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -59,6 +59,28 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } + let heading = __( "Don't worry about a thing", 'jetpack-protect' ); + if ( numThreats > 0 ) { + if ( hasPlan ) { + heading = sprintf( + /* translators: %s: Total number of threats */ + _n( '%1$s active threat', '%1$s active threats', numThreats, 'jetpack-protect' ), + numThreats + ); + } else { + heading = sprintf( + /* translators: %s: Total number of vulnerabilities */ + _n( + '%1$s active vulnerability', + '%1$s active vulnerabilities', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + } + const handleShowAutoFixersClick = threatList => { return event => { event.preventDefault(); @@ -84,94 +106,79 @@ const ScanAdminSectionHero: React.FC = () => { } return ( - - - { lastCheckedLocalTimestamp - ? sprintf( - // translators: %s: date and time of the last scan - __( '%s results', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) - ) - : __( 'Most recent results', 'jetpack-protect' ) } + + + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS, g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + + 0 ? 'error' : 'success' }> + { heading } + + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } - - 0 ? 'error' : 'success' }> - { numThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s active %2$s', 'jetpack-protect' ), - numThreats, - hasPlan - ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) - : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) - ) - : __( "Don't worry about a thing", 'jetpack-protect' ) } - - - <> - { hasPlan ? ( - - { __( - "We actively review your site's files line-by-line to identify threats and vulnerabilities.", - 'jetpack-protect' - ) } - - ) : ( - <> - - { sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - + ) : ( + <> + + { sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted ) } - { fixableList.length > 0 && ( - <> -
    - -
    -
    + -
    - - } - /> + > + + + + ) } + { fixableList.length > 0 && ( + <> +
    + +
    +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx index 4db4449b60119..ac9e0137cd170 100644 --- a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx @@ -18,44 +18,38 @@ const ScanningAdminSectionHero: React.FC = () => { : totalVulnerabilities.toLocaleString(); return ( - - - { __( 'Your results will be ready soon', 'jetpack-protect' ) } - - - <> - { hasPlan && ( - - ) } - - { hasPlan - ? __( - "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", - 'jetpack-protect' - ) - : sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - } - secondary={ } - preserveSecondaryOnMobile={ false } - spacing={ 4 } - /> + + + + { __( 'Your results will be ready soon', 'jetpack-protect' ) } + + { hasPlan && ( + + ) } + + { hasPlan + ? __( + "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", + 'jetpack-protect' + ) + : sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted + ) } + + + + + + ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 163fd23248aaa..5806ca5353863 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,3 +1,7 @@ +.scanning-main { + max-width: 512px; +} + .auto-fixers { margin-top: calc( var( --spacing-base ) * 4 ); // 32px } @@ -12,3 +16,9 @@ margin-right: calc( var( --spacing-base ) * -3 ); // -24px } } + +.progress-animation { + @media (max-width: 1099px) { + display: none; + } +} From 38c89046b963b48cc94904b133113645338fd8e1 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 9 Dec 2024 15:05:17 -0800 Subject: [PATCH 094/290] Fix description text wrap --- .../components/components/threats-data-views/styles.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index 3bafdf3af12f5..fcb2f65b1dca9 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -20,6 +20,7 @@ .threat__description { font-size: 12px; + white-space: wrap; } .threat__fixedOn, From 5892e037b69e9da7bf178f2e606258b82f1ebd04 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 10 Dec 2024 10:33:25 -0800 Subject: [PATCH 095/290] Restore history header --- .../scan/history-admin-section-hero.tsx | 68 +++++++++++++++++++ .../protect/src/js/routes/scan/index.jsx | 4 +- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 projects/plugins/protect/src/js/routes/scan/history-admin-section-hero.tsx diff --git a/projects/plugins/protect/src/js/routes/scan/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history-admin-section-hero.tsx new file mode 100644 index 0000000000000..b6222d6317a94 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/scan/history-admin-section-hero.tsx @@ -0,0 +1,68 @@ +import { Text } from '@automattic/jetpack-components'; +import { dateI18n } from '@wordpress/date'; +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import AdminSectionHero from '../../components/admin-section-hero'; +import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; +import useHistoryQuery from '../../data/scan/use-history-query'; +import styles from './styles.module.scss'; + +const HistoryAdminSectionHero: React.FC = () => { + const { data: history } = useHistoryQuery(); + const numThreats = history ? history.threats.length : 0; + + const oldestFirstDetected = useMemo( () => { + if ( ! history ) { + return null; + } + + return history.threats.reduce( ( oldest, current ) => { + return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) + ? current + : oldest; + } ).firstDetected; + }, [ history ] ); + + if ( history && ! history.error ) { + return ( + + ); + } + + return ( + + + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + + 0 ? 'error' : 'success' }> + { numThreats > 0 + ? sprintf( + /* translators: %s: Total number of threats */ + __( '%1$s previously active %2$s', 'jetpack-protect' ), + numThreats, + numThreats === 1 ? 'threat' : 'threats' + ) + : __( 'No previously active threats', 'jetpack-protect' ) } + + { __( 'Here you can view all of your threats to date.', 'jetpack-protect' ) } + + + ); +}; + +export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index c56ae3c747f3e..120bf61930a8d 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -7,6 +7,7 @@ import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-s import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; +import HistoryAdminSectionHero from './history-admin-section-hero'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; import ScanResultsDataView from './scan-results-data-view'; @@ -26,6 +27,7 @@ const ScanPage = () => { const { data: status } = useScanStatusQuery( { usePolling: true } ); const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + const [ isViewingHistory, setIsViewingHistory ] = useState( false ); let currentScanStatus; if ( status.error ) { @@ -68,7 +70,7 @@ const ScanPage = () => { return ( - + { isViewingHistory ? : } Date: Tue, 10 Dec 2024 11:47:02 -0800 Subject: [PATCH 096/290] Pass status filter presets to consumer --- .../components/threats-data-views/index.tsx | 40 +++++++++++++++- .../threats-status-toggle-group-control.tsx | 47 ++++--------------- .../scan/history-admin-section-hero.tsx | 2 +- .../protect/src/js/routes/scan/index.jsx | 15 ++++-- .../js/routes/scan/scan-results-data-view.tsx | 8 +++- 5 files changed, 66 insertions(+), 46 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 6af7c028f4c3a..cdf737316334c 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,4 +1,4 @@ -import { getThreatType, type Threat } from '@automattic/jetpack-scan'; +import { getThreatType, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; import { type Action, type ActionButton, @@ -14,7 +14,7 @@ import { import { dateI18n } from '@wordpress/date'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState, useEffect } from 'react'; import Badge from '../badge'; import ThreatFixerButton from '../threat-fixer-button'; import ThreatSeverityBadge from '../threat-severity-badge'; @@ -56,6 +56,7 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing. * @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring. * @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring. + * @param {Function} props.onStatusFilterChange - Callback function run when the status filter changes. * * @return {JSX.Element} The ThreatsDataViews component. */ @@ -69,6 +70,7 @@ export default function ThreatsDataViews( { onFixThreats, onIgnoreThreats, onUnignoreThreats, + onStatusFilterChange, }: { data: Threat[]; filters?: Filter[]; @@ -79,6 +81,7 @@ export default function ThreatsDataViews( { onFixThreats?: ( threats: Threat[] ) => void; onIgnoreThreats?: ActionButton< Threat >[ 'callback' ]; onUnignoreThreats?: ActionButton< Threat >[ 'callback' ]; + onStatusFilterChange?: ( newStatus: 'active' | 'historic' | null ) => void; } ): JSX.Element { const baseView = { sort: { @@ -501,6 +504,33 @@ export default function ThreatsDataViews( { isThreatEligibleForUnignore, ] ); + /** + * Memoized function to determine if a status filter is selected. + * + * @param {Array} threatStatuses - List of threat statuses. + */ + const isStatusFilterSelected = useMemo( + () => ( threatStatuses: ThreatStatus[] ) => + view.filters.some( + filter => + filter.field === 'status' && + Array.isArray( filter.value ) && + filter.value.length === threatStatuses.length && + threatStatuses.every( threatStatus => filter.value.includes( threatStatus ) ) + ), + [ view.filters ] + ); + + const selectedStatusFilter = useMemo( () => { + if ( isStatusFilterSelected( [ 'current' ] ) ) { + return 'active' as const; + } + if ( isStatusFilterSelected( [ 'fixed', 'ignored' ] ) ) { + return 'historic' as const; + } + return null; + }, [ isStatusFilterSelected ] ); + /** * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. * @@ -526,6 +556,11 @@ export default function ThreatsDataViews( { */ const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] ); + // Notify the consumer whenever the selectedStatusFilter changes + useEffect( () => { + onStatusFilterChange?.( selectedStatusFilter ); + }, [ selectedStatusFilter, onStatusFilterChange ] ); + return ( } /> diff --git a/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx b/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx index be7b252b80c22..8facad3447ecc 100644 --- a/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx +++ b/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx @@ -1,4 +1,4 @@ -import { type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; +import { type Threat } from '@automattic/jetpack-scan'; import { __experimentalToggleGroupControl as ToggleGroupControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis __experimentalToggleGroupControlOption as ToggleGroupControlOption, // eslint-disable-line @wordpress/no-unsafe-wp-apis @@ -10,19 +10,23 @@ import styles from './styles.module.scss'; /** * ToggleGroupControl component for filtering threats by status. - * @param {object} props - Component props. - * @param { Threat[]} props.data - Threats data. - * @param { View } props.view - The current view. - * @param { Function } props.onChangeView - Callback function to handle view changes. + * @param {object} props - Component props. + * @param { Threat[]} props.data - Threats data. + * @param { View } props.view - The current view. + * @param { string } props.selectedStatusFilter - The selected status filter. + * @param { Function } props.onChangeView - Callback function to handle view changes. + * * @return {JSX.Element|null} The component or null. */ export default function ThreatsStatusToggleGroupControl( { data, view, + selectedStatusFilter, onChangeView, }: { data: Threat[]; view: View; + selectedStatusFilter: string; onChangeView: ( newView: View ) => void; } ): JSX.Element { /** @@ -87,43 +91,12 @@ export default function ThreatsStatusToggleGroupControl( { [ view, onChangeView ] ); - /** - * Memoized function to determine if a status filter is selected. - * - * @param {Array} threatStatuses - List of threat statuses. - */ - const isStatusFilterSelected = useMemo( - () => ( threatStatuses: ThreatStatus[] ) => - view.filters.some( - filter => - filter.field === 'status' && - Array.isArray( filter.value ) && - filter.value.length === threatStatuses.length && - threatStatuses.every( threatStatus => filter.value.includes( threatStatus ) ) - ), - [ view.filters ] - ); - - const selectedValue = useMemo( () => { - if ( isStatusFilterSelected( [ 'current' ] ) ) { - return 'active' as const; - } - if ( isStatusFilterSelected( [ 'fixed', 'ignored' ] ) ) { - return 'historic' as const; - } - return '' as const; - }, [ isStatusFilterSelected ] ); - - if ( ! ( activeThreatsCount + historicThreatsCount ) ) { - return null; - } - try { return (
    { } ).firstDetected; }, [ history ] ); - if ( history && ! history.error ) { + if ( history && history.error ) { return ( { const { data: status } = useScanStatusQuery( { usePolling: true } ); const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); - const [ isViewingHistory, setIsViewingHistory ] = useState( false ); + const [ statusFilter, setStatusFilter ] = useState( 'active' ); + + const handleStatusFilterChange = useCallback( newStatusFilter => { + setStatusFilter( newStatusFilter ); + }, [] ); let currentScanStatus; if ( status.error ) { @@ -70,7 +74,7 @@ const ScanPage = () => { return ( - { isViewingHistory ? : } + { 'historic' === statusFilter ? : } { >
    - +
    { !! status && ! isScanInProgress( status ) && ( [ 'filters' ]; + onStatusFilterChange: ( newStatus: 'active' | 'historic' | null ) => void; } ) { const { setModal } = useModal(); @@ -51,6 +54,7 @@ export default function ScanResultsDataView( { onFixThreats={ onFixThreats } onIgnoreThreats={ onIgnoreThreats } onUnignoreThreats={ onUnignoreThreats } + onStatusFilterChange={ onStatusFilterChange } /> ); } From 3ad2ee88cc1ca490b3e29c3593767f556f5ccf77 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 10 Dec 2024 11:50:26 -0800 Subject: [PATCH 097/290] Restore early return --- .../threats-status-toggle-group-control.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx b/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx index 8facad3447ecc..fb88f1d3b8077 100644 --- a/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx +++ b/projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx @@ -91,6 +91,10 @@ export default function ThreatsStatusToggleGroupControl( { [ view, onChangeView ] ); + if ( ! ( activeThreatsCount + historicThreatsCount ) ) { + return null; + } + try { return (
    From 7ca9b4148801d4f3275f92de1f5c30ffad8db116 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 10 Dec 2024 12:04:57 -0800 Subject: [PATCH 098/290] Add plan level restrictions --- .../components/threats-data-views/index.tsx | 49 ++++++++++--------- .../protect/src/js/routes/scan/index.jsx | 4 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index cdf737316334c..41e608dc2434a 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -256,28 +256,6 @@ export default function ThreatsDataViews( { ); }, }, - { - id: THREAT_FIELD_STATUS, - label: __( 'Status', 'jetpack-components' ), - elements: THREAT_STATUSES, - getValue( { item }: { item: Threat } ) { - if ( ! item.status ) { - return 'current'; - } - return ( - THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? item.status - ); - }, - render( { item }: { item: Threat } ) { - if ( item.status ) { - const status = THREAT_STATUSES.find( ( { value } ) => value === item.status ); - if ( status ) { - return { status.label }; - } - } - return { __( 'Active', 'jetpack-components' ) }; - }, - }, { id: THREAT_FIELD_TYPE, label: __( 'Type', 'jetpack-components' ), @@ -329,6 +307,33 @@ export default function ThreatsDataViews( { return item.extension ? item.extension.slug : ''; }, }, + ...( dataFields.includes( 'status' ) + ? [ + { + id: THREAT_FIELD_STATUS, + label: __( 'Status', 'jetpack-components' ), + elements: THREAT_STATUSES, + getValue( { item }: { item: Threat } ) { + if ( ! item.status ) { + return 'current'; + } + return ( + THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? + item.status + ); + }, + render( { item }: { item: Threat } ) { + if ( item.status ) { + const status = THREAT_STATUSES.find( ( { value } ) => value === item.status ); + if ( status ) { + return { status.label }; + } + } + return { __( 'Active', 'jetpack-components' ) }; + }, + }, + ] + : [] ), ...( dataFields.includes( 'severity' ) ? [ { diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 7fe21dfb44cb8..3236f717a51eb 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -84,8 +84,8 @@ const ScanPage = () => {
    { !! status && ! isScanInProgress( status ) && ( From 3c2403050fdf29f33e766bebe765a51d41cb6ac0 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 14 Nov 2024 10:42:12 -0700 Subject: [PATCH 099/290] Init project branch From be99f508195a44304f555773f6b56dd010d4b563 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:03:36 -0800 Subject: [PATCH 100/290] Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller --- .../changelog/add-protect-header-buttons | 4 +++ .../src/js/components/admin-page/index.jsx | 27 +++++++++++++++++-- .../components/admin-page/styles.module.scss | 10 +++++++ .../src/js/components/scan-button/index.jsx | 4 +++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/protect/changelog/add-protect-header-buttons diff --git a/projects/plugins/protect/changelog/add-protect-header-buttons b/projects/plugins/protect/changelog/add-protect-header-buttons new file mode 100644 index 0000000000000..24c40f542d7ee --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-header-buttons @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Adds Go to Cloud and Scan now buttons to the primary header diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4579831b5f0a5..4e93ae443aa72 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -1,16 +1,20 @@ import { AdminPage as JetpackAdminPage, + Button, Container, + getRedirectUrl, JetpackProtectLogo, } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import useNotices from '../../hooks/use-notices'; +import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; +import ScanButton from '../scan-button'; import Tabs, { Tab } from '../tabs'; import styles from './styles.module.scss'; @@ -24,6 +28,8 @@ const AdminPage = ( { children } ) => { current: { threats: numThreats }, }, } = useProtectData(); + const location = useLocation(); + const { hasPlan } = usePlan(); // Redirect to the setup page if the site is not registered. useEffect( () => { @@ -36,10 +42,27 @@ const AdminPage = ( { children } ) => { return null; } + const viewingScanPage = location.pathname.includes( '/scan' ); + + const { siteSuffix, blogID } = window.jetpackProtectInitialState || {}; + const goToCloudUrl = getRedirectUrl( 'jetpack-scan-dash', { site: blogID ?? siteSuffix } ); + return ( } + header={ +
    + + { hasPlan && viewingScanPage && ( +
    + + +
    + ) } +
    + } > { notice && } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index e70d2cdb076c7..adf7dc594b907 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From 8dd5ddec04819c96590e145137c42fbeb15f20c4 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 101/290] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 9c8f30b7b8067..4aa517f5f120b 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import AdminSectionHero from '../../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } + { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..1c5cc6cac49b9 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; +import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useFixers from '../../hooks/use-fixers'; +import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return list.filter( threat => { + const threatId = parseInt( threat.id ); + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From 90d6898a482356b03a112beb4164a45ae3c76e9b Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 102/290] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
    - diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adf7dc594b907..adc0cee561ba5 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From dd219135b600bb73dda0fe90e6122dbfdcb79e45 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 29 Nov 2024 21:00:37 -0700 Subject: [PATCH 103/290] Protect: add ShieldIcon component --- .../components/admin-section-hero/index.tsx | 21 ++- .../stories/index.stories.jsx | 4 +- .../src/js/components/shield-icon/index.tsx | 165 ++++++++++++++++++ .../shield-icon/stories/index.stories.tsx | 50 ++++++ 4 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ed83bebc8638..758c8c21e0193 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,9 +1,6 @@ -import { - AdminSectionHero as JetpackAdminSectionHero, - H3, - getIconBySlug, -} from '@automattic/jetpack-components'; +import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; +import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -15,7 +12,7 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean } >; + Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -44,17 +41,23 @@ const AdminSectionHero: AdminSectionHeroComponent = ( { AdminSectionHero.Heading = ( { children, + variant = 'default', showIcon = false, }: { children: React.ReactNode; + variant?: 'default' | 'success' | 'error'; showIcon?: boolean; } ) => { - const Icon = getIconBySlug( 'protect' ); - return (

    { children } - { showIcon && } + { showIcon && ( + + ) }

    ); }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index 7d5b4f8066c93..ca2dfda7fc98e 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -12,7 +12,9 @@ Default.args = { main: ( <> - { 'No threats found' } + + { 'No threats found' } + { 'Most recent results' } diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..3bf7f479f0051 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/index.tsx @@ -0,0 +1,165 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.variant - Icon variant. + * @param {string} props.fill - Icon fill color. + * @param {string} props.className - Additional class names. + * @param {number} props.height - Icon height. + * @return {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ShieldIcon( { + variant = 'default', + height = 32, + className, + fill, +}: { + variant: + | 'default' + | 'success' + | 'error' + | 'default-outline' + | 'success-outline' + | 'error-outline'; + className?: string; + height?: number; + fill?: string; +} ): JSX.Element { + if ( 'error-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'error' === variant ) { + return ( + + + + + ); + } + + if ( 'success-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'success' === variant ) { + return ( + + + + + ); + } + + if ( 'default-outline' === variant ) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..d10365f4b0834 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ShieldIcon from '../index'; + +export default { + title: 'Plugins/Protect/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
    + +
    + ), + ], + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ + 'default', + 'success', + 'error', + 'default-outline', + 'success-outline', + 'error-outline', + ], + }, + fill: { + control: 'color', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'default', +}; + +export const SuccessVariant = args => ; +SuccessVariant.args = { + variant: 'success', +}; + +export const ErrorVariant = args => ; +ErrorVariant.args = { + variant: 'error', +}; From 19c083e928a402064ebafeab11ead2bea81b7d3f Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 4 Dec 2024 20:26:54 -0700 Subject: [PATCH 104/290] Protect: Add ShieldIcon Component (#40402) --- .../components/changelog/add-shield-icon | 4 + .../components/shield-icon/index.tsx | 79 +++++++++ .../shield-icon/stories/index.stories.tsx | 54 ++++++ projects/js-packages/components/index.ts | 1 + .../protect/changelog/refactor-alert-icon | 5 + .../components/admin-section-hero/index.tsx | 20 ++- .../admin-section-hero/styles.module.scss | 4 +- .../src/js/components/alert-icon/index.jsx | 74 -------- .../alert-icon/stories/index.stories.jsx | 17 -- .../components/alert-icon/styles.module.scss | 11 -- .../src/js/components/shield-icon/index.tsx | 165 ------------------ .../shield-icon/stories/index.stories.tsx | 50 ------ .../history/history-admin-section-hero.tsx | 2 +- .../routes/scan/scan-admin-section-hero.tsx | 2 +- 14 files changed, 162 insertions(+), 326 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-shield-icon create mode 100644 projects/js-packages/components/components/shield-icon/index.tsx create mode 100644 projects/js-packages/components/components/shield-icon/stories/index.stories.tsx create mode 100644 projects/plugins/protect/changelog/refactor-alert-icon delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/js-packages/components/changelog/add-shield-icon b/projects/js-packages/components/changelog/add-shield-icon new file mode 100644 index 0000000000000..5c6cc27eeb809 --- /dev/null +++ b/projects/js-packages/components/changelog/add-shield-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add ShieldIcon component diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..fee9f4d70c463 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +const COLORS = { + error: '#D63638', + warning: '#F0B849', + success: '#069E08', + default: '#1d2327', +}; + +/** + * Protect Shield SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.className - Additional class names. + * @param {string} props.contrast - Icon contrast color. Overrides variant. + * @param {string} props.fill - Icon fill color (default, success, warning, error, or a custom color code string). Overrides variant. + * @param {number} props.height - Icon height (px). Width is calculated based on height. + * @param {string} props.icon - Icon variant (success, error). Overrides variant. + * @param {boolean} props.outline - When enabled, the icon will use an outline style. + * @param {string} props.variant - Icon variant (default, success, error). + * + * @return {React.ReactElement} Protect Shield SVG Icon + */ +export default function ShieldIcon( { + className, + contrast = '#fff', + fill, + height = 32, + icon, + outline = false, + variant = 'default', +}: { + className?: string; + contrast?: string; + fill?: 'default' | 'success' | 'warning' | 'error' | string; + height?: number; + icon?: 'success' | 'error'; + outline?: boolean; + variant: 'default' | 'success' | 'warning' | 'error'; +} ): JSX.Element { + const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; + const iconFill = outline ? shieldFill : contrast; + const iconVariant = icon || variant; + + return ( + + + { 'success' === iconVariant && ( + + ) } + { [ 'warning', 'error' ].includes( iconVariant ) && ( + + ) } + + ); +} diff --git a/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..b5a16d4da4075 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,54 @@ +import ShieldIcon from '../index'; + +export default { + title: 'JS Packages/Components/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ 'default', 'success', 'warning', 'error' ], + }, + icon: { + control: { + type: 'select', + }, + options: [ 'success', 'error' ], + }, + fill: { + control: 'color', + }, + outline: { + control: 'boolean', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'success', + outline: false, +}; + +export const Variants = () => { + return ( +
    +
    + + + + +
    +
    + + + + +
    +
    + ); +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index eb90df97ad5fe..4b0f3612012e7 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ShieldIcon } from './components/shield-icon'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/plugins/protect/changelog/refactor-alert-icon b/projects/plugins/protect/changelog/refactor-alert-icon new file mode 100644 index 0000000000000..46b4c247b1b9f --- /dev/null +++ b/projects/plugins/protect/changelog/refactor-alert-icon @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Refactored icon component code. + + diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 758c8c21e0193..5ccf607698084 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,6 +1,9 @@ -import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; +import { + AdminSectionHero as JetpackAdminSectionHero, + H3, + ShieldIcon, +} from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; -import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -12,7 +15,12 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; + Heading: React.FC< { + children: React.ReactNode; + showIcon?: boolean; + variant?: 'default' | 'success' | 'error'; + outline?: boolean; + } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -53,8 +61,10 @@ AdminSectionHero.Heading = ( { { children } { showIcon && ( ) } diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index 5881bcd910045..a414aa9216f5c 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -13,7 +13,7 @@ } .heading-icon { - margin-left: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } @@ -23,4 +23,4 @@ .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px -} \ No newline at end of file +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
    - - - - - - - - - - - - - - - - - - - -
    - ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx deleted file mode 100644 index 47b2ee32d4b51..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import AlertIcon from '../index.jsx'; - -export default { - title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, -}; - -const FooterTemplate = args => ; -export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx deleted file mode 100644 index 3bf7f479f0051..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @param {object} props - Component props. - * @param {string} props.variant - Icon variant. - * @param {string} props.fill - Icon fill color. - * @param {string} props.className - Additional class names. - * @param {number} props.height - Icon height. - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ShieldIcon( { - variant = 'default', - height = 32, - className, - fill, -}: { - variant: - | 'default' - | 'success' - | 'error' - | 'default-outline' - | 'success-outline' - | 'error-outline'; - className?: string; - height?: number; - fill?: string; -} ): JSX.Element { - if ( 'error-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'error' === variant ) { - return ( - - - - - ); - } - - if ( 'success-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'success' === variant ) { - return ( - - - - - ); - } - - if ( 'default-outline' === variant ) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx deleted file mode 100644 index d10365f4b0834..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ShieldIcon from '../index'; - -export default { - title: 'Plugins/Protect/Sheild Icon', - component: ShieldIcon, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], - argTypes: { - variant: { - control: { - type: 'select', - }, - options: [ - 'default', - 'success', - 'error', - 'default-outline', - 'success-outline', - 'error-outline', - ], - }, - fill: { - control: 'color', - }, - }, -}; - -export const Default = args => ; -Default.args = { - variant: 'default', -}; - -export const SuccessVariant = args => ; -SuccessVariant.args = { - variant: 'success', -}; - -export const ErrorVariant = args => ; -ErrorVariant.args = { - variant: 'error', -}; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 4aa517f5f120b..141c51cde284a 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -60,7 +60,7 @@ const HistoryAdminSectionHero: React.FC = () => { __( 'Most recent results', 'jetpack-protect' ) ) } - + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 1c5cc6cac49b9..9e1b9c102a037 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -95,7 +95,7 @@ const ScanAdminSectionHero: React.FC = () => { anchor={ dailyScansPopoverAnchor } /> ) } - + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ From 08cdcb26c40d063df52faf508817eb060a0ff43a Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 5 Dec 2024 10:50:27 -0700 Subject: [PATCH 105/290] Protect: Integrate ThreatsDataViews Component (#40076) --- pnpm-lock.yaml | 3 + .../add-threat-subtitle-and-icon-utils | 4 + projects/js-packages/scan/src/utils/index.ts | 32 +- .../protect/changelog/add-threats-data-views | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 30 +- projects/plugins/protect/src/js/api.ts | 3 +- .../src/js/components/admin-page/index.jsx | 12 +- .../components/admin-page/styles.module.scss | 11 + .../error-admin-section-hero/index.tsx | 4 - .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 14 +- .../src/js/components/navigation/badge.jsx | 101 ------ .../src/js/components/navigation/group.jsx | 51 --- .../src/js/components/navigation/index.jsx | 73 ----- .../src/js/components/navigation/item.jsx | 85 ----- .../src/js/components/navigation/label.jsx | 24 -- .../components/navigation/styles.module.scss | 142 --------- .../navigation/use-menu-navigation.js | 92 ------ .../js/components/paid-accordion/index.jsx | 192 ----------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 202 ------------ .../src/js/components/pricing-table/index.jsx | 4 +- .../components/protect-check-icon/index.tsx | 25 -- .../js/components/scan-navigation/index.jsx | 44 --- .../js/components/threat-fix-header/index.jsx | 7 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 -------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 -------- .../js/components/threats-list/pagination.jsx | 142 --------- .../js/components/threats-list/paid-list.jsx | 253 --------------- .../threats-list/styles.module.scss | 129 -------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/hooks/use-protect-data/index.ts | 173 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../protect/src/js/routes/firewall/index.jsx | 3 +- .../history/history-admin-section-hero.tsx | 84 ----- .../src/js/routes/scan/history/index.jsx | 301 ------------------ .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 37 --- .../protect/src/js/routes/scan/index.jsx | 79 +++-- .../src/js/routes/scan/onboarding-steps.jsx | 61 ++-- .../routes/scan/scan-admin-section-hero.tsx | 138 ++++---- .../src/js/routes/scan/scan-footer.jsx | 143 --------- .../js/routes/scan/scan-results-data-view.tsx | 56 ++++ .../scan/scanning-admin-section-hero.tsx | 25 +- .../src/js/routes/scan/styles.module.scss | 20 +- projects/plugins/protect/webpack.config.js | 18 ++ 53 files changed, 363 insertions(+), 3668 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils create mode 100644 projects/plugins/protect/changelog/add-threats-data-views delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/badge.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/group.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/item.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/label.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/scan-navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/routes/scan/scan-footer.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57a36196e0416..01b39d0c6f7f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4220,6 +4220,9 @@ importers: specifier: 6.2.2 version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@automattic/babel-plugin-replace-textdomain': + specifier: workspace:* + version: link:../../js-packages/babel-plugin-replace-textdomain '@automattic/jetpack-webpack-config': specifier: workspace:* version: link:../../js-packages/webpack-config diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils new file mode 100644 index 0000000000000..ad8fa81458278 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add utilities for generating threat subtitle and icons diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 945cd0ecb7fa8..9e2e75bcd4d91 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => { return null; }; +export const getThreatIcon = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return 'wordpress-alt'; + case 'plugin': + return 'plugins'; + case 'theme': + return 'appearance'; + case 'file': + return 'media-code'; + default: + return 'shield-alt'; + } +}; + +export const getThreatSubtitle = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); + case 'plugin': + return __( 'Vulnerable Plugin', 'jetpack-scan' ); + case 'theme': + return __( 'Vulnerable Theme', 'jetpack-scan' ); + case 'file': + return __( 'File Threat', 'jetpack-scan' ); + default: + return __( 'Threat', 'jetpack-scan' ); + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); @@ -123,7 +153,7 @@ export const getFixerDescription = ( threat: Threat ) => { } break; case 'update': - if ( threat.fixedIn && threat.extension.name ) { + if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ __( 'Update %1$s to version %2$s', 'jetpack-scan' ), diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views new file mode 100644 index 0000000000000..e15bd6a461a71 --- /dev/null +++ b/projects/plugins/protect/changelog/add-threats-data-views @@ -0,0 +1,5 @@ +Significance: minor +Type: changed + +Added DataViews component for viewing scan results. + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index 9d11e44dc60af..8708e2c101c9f 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -49,6 +49,7 @@ "react-router-dom": "6.2.2" }, "devDependencies": { + "@automattic/babel-plugin-replace-textdomain": "workspace:*", "@automattic/jetpack-webpack-config": "workspace:*", "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index bd034c375caf9..8ea1dec7156e7 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -207,43 +207,19 @@ public static function fetch_from_api() { * Normalize API Data * Formats the payload from the Scan API into an instance of History_Model. * - * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility. - * * @param object $scan_data The data returned by the scan API. * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..97d11fd5c0f2b 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,6 +1,7 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { WafStatus } from './types/waf'; const API = { getWaf: (): Promise< WafStatus > => diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 2d023560517f3..68f9359a9bd81 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useNotices from '../../hooks/use-notices'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; import ScanButton from '../scan-button'; @@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => { const { isRegistered } = useConnection(); const { isSeen: wafSeen } = useWafData(); const navigate = useNavigate(); - const { - counts: { - current: { threats: numThreats }, - }, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); const location = useLocation(); const { hasPlan } = usePlan(); @@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => { link="/scan" label={ - { numThreats > 0 + { status.threats.length > 0 ? sprintf( // translators: %d is the number of threats found. __( 'Scan (%d)', 'jetpack-protect' ), - numThreats + status.threats.length ) : __( 'Scan', 'jetpack-protect' ) } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adc0cee561ba5..da2e9510cd7d9 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -5,10 +5,21 @@ .header { display: flex; justify-content: space-between; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px + align-items: center; + + @media ( min-width: 600px ) { + flex-direction: row; + } &__scan_buttons { display: flex; gap: calc( var( --spacing-base ) * 3 ); // 24px + + @media ( min-width: 600px ) { + flex-direction: row; + } } } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 5214531dcf362..1a9bc87387fa9 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; -import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; interface ErrorAdminSectionHeroProps { @@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { { displayErrorMessage } -
    - -
    } preserveSecondaryOnMobile={ false } diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
    - +
    diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx index 7e8113b6f38ab..0788eb8bd7a41 100644 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx @@ -1,16 +1,18 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx deleted file mode 100644 index 93ebecf7235ef..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/badge.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, check, info } from '@wordpress/icons'; -import PropTypes from 'prop-types'; -import React, { useState, useCallback, useMemo } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import styles from './styles.module.scss'; - -/** - * Gets the Badge element - * - * @param {number} count - The number of threats found for this item. - * @param {boolean} checked - Whether this item was checked for threats yet. - * @return {object} The badge element - */ -const getBadgeElement = ( count, checked ) => { - if ( ! checked ) { - return { - popoverText: __( - 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', - 'jetpack-protect' - ), - badgeElement: ( - - ), - }; - } - - if ( count === 0 ) { - return { - popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ), - badgeElement: ( - - ), - }; - } - - return { - popoverText: null, - badgeElement: ( - - { count } - - ), - }; -}; - -const ItemBadge = ( { count, checked } ) => { - const { data: status } = useScanStatusQuery(); - - const { popoverText, badgeElement } = getBadgeElement( count, checked ); - const [ showPopover, setShowPopover ] = useState( false ); - - const inProgress = useMemo( () => isScanInProgress( status ), [ status ] ); - - const handleEnter = useCallback( () => { - if ( inProgress ) { - return; - } - - setShowPopover( true ); - }, [ inProgress ] ); - - const handleOut = useCallback( () => { - setShowPopover( false ); - }, [] ); - - return ( -
    - { ! inProgress ? badgeElement : } - { showPopover && ( - - - { popoverText } - - - ) } -
    - ); -}; - -ItemBadge.propTypes = { - /* The number of threats found for this item */ - count: PropTypes.number, - /* Whether this item was checked for threats yet */ - checked: PropTypes.bool, -}; - -export default ItemBadge; diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx deleted file mode 100644 index 9352ae5c63d67..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/group.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useState, useCallback, useContext } from 'react'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const MAX_ITEMS = 8; - -const NavigationGroup = ( { icon, label, children } ) => { - const [ collapsed, setCollapsed ] = useState( true ); - const { mode } = useContext( NavigationContext ); - const needsTruncate = - Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list'; - const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children; - const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0; - - const handleCollapsedToggle = useCallback( () => { - setCollapsed( current => ! current ); - }, [] ); - - return ( -
  • - - { label } - -
    -
      { content }
    - { needsTruncate && ( -
    - -
    - ) } -
    -
  • - ); -}; - -export default NavigationGroup; diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx deleted file mode 100644 index bd30dfbdad964..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import React, { useState, useRef, useCallback } from 'react'; -import NavigationGroup from './group'; -import NavigationItem from './item'; -import styles from './styles.module.scss'; -import useMenuNavigation, { NavigationContext } from './use-menu-navigation'; - -const NavigationList = ( { children } ) => ( -
      - { children } -
    -); - -const NavigationDropdown = ( { children, data } ) => { - const ref = useRef( undefined ); - const [ listOpen, setListOpen ] = useState( false ); - const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? { - label: __( 'See all results', 'jetpack-protect' ), - }; - const { label, icon } = item; - - const handleOpen = useCallback( () => { - setListOpen( open => ! open ); - }, [] ); - - return ( - - ); -}; - -const getNavigationComponent = mode => { - switch ( mode ) { - case 'list': - return NavigationList; - case 'dropdown': - return NavigationDropdown; - default: - return NavigationList; - } -}; - -const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => { - const data = useMenuNavigation( { selected, onSelect } ); - const Component = getNavigationComponent( mode ); - - return ( - - { children } - - ); -}; - -export default Navigation; -export { NavigationItem, NavigationGroup }; diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx deleted file mode 100644 index d902625c3997d..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/item.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import clsx from 'clsx'; -import React, { useContext, useEffect, useCallback } from 'react'; -import ItemBadge from './badge'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const NavigationItem = ( { - id, - label, - icon, - badge, - disabled, - onClick, - onKeyDown, - onFocus, - checked, -} ) => { - const context = useContext( NavigationContext ); - - const selected = context?.selectedItem === id; - const registerItem = context?.registerItem; - const registerRef = context?.registerRef; - const handleClickItem = context?.handleClickItem; - const handleKeyDownItem = context?.handleKeyDownItem; - const handleFocusItem = context?.handleFocusItem; - - const wrapperClassName = clsx( styles[ 'navigation-item' ], { - [ styles.clickable ]: ! disabled, - [ styles.selected ]: selected, - } ); - - const handleClick = useCallback( - evt => { - onClick?.( evt ); - handleClickItem?.( id ); - }, - [ handleClickItem, id, onClick ] - ); - - const handleKeyDown = useCallback( - evt => { - onKeyDown?.( evt ); - handleKeyDownItem?.( evt ); - }, - [ handleKeyDownItem, onKeyDown ] - ); - - const handleRef = useCallback( - ref => { - registerRef( ref, id ); - }, - [ registerRef, id ] - ); - - const handleFocus = useCallback( - evt => { - onFocus?.( evt ); - handleFocusItem?.( id ); - }, - [ handleFocusItem, id, onFocus ] - ); - - useEffect( () => { - registerItem( { id, disabled, label, icon } ); - // eslint-disable-next-line - }, [] ); - - return ( -
  • - { label } - -
  • - ); -}; - -export default NavigationItem; diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx deleted file mode 100644 index 8f075caae020a..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/label.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon } from '@wordpress/icons'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './styles.module.scss'; - -const ItemLabel = ( { icon, children, className } ) => { - return ( - - { icon && } - { children } - - ); -}; - -ItemLabel.propTypes = { - /* An icon that will be rendered before text */ - icon: PropTypes.node, - /* Label text that will be rendered */ - children: PropTypes.node.isRequired, -}; - -export default ItemLabel; diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss deleted file mode 100644 index df9e3ef1f8a25..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss +++ /dev/null @@ -1,142 +0,0 @@ -.navigation { - background-color: var( --jp-white ); - box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); - border-radius: var( --jp-border-radius ); - margin: 0; -} - -.navigation-item { - display: flex; - padding: calc( var( --spacing-base ) * 2 ); // 16px; - align-items: center; - justify-content: space-between; - margin: 0; - text-align: left; - - // Clickable State - &.clickable { - cursor: pointer; - outline-color: var( --jp-black ); - - // Focus/Hover State - &:hover:not(.selected), - &:focus:not(.selected) { - background-color: var( --jp-gray-0 ); - } - } - - // Selected State - &.selected { - background-color: var( --jp-black ); - - & .navigation-item-label { - color: var( --jp-white ) - } - - & .navigation-item-icon { - fill: var( --jp-white ); - } - - & .navigation-item-badge { - border: 1px solid var( --jp-red ); - background-color: var( --jp-red ); - color: var( --jp-white ); - } - } - - // CHILDRENS - - // .navigation-item-label - &-label { - display: flex; - align-items: center; - padding-right: var( --spacing-base ); // 8px - overflow-x: hidden; - } - - // .navigation-item-label-content - &-label-text { - display: block; - overflow-x: hidden; - text-overflow: ellipsis; - } - - // .navigation-item-icon - &-icon { - margin-right: calc( var( --spacing-base ) * 2); // 16px - } - - // .navigation-item-badge - &-badge { - border: 1px solid var( --jp-red-60 ); - color: var( --jp-red-60 ); - border-radius: 50%; - padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px - min-width: 30px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - &-check-badge { - fill: var( --jp-green-50 ); - } - - &-info-badge { - fill: var( --jp-gray-20 ); - } -} - -.navigation-group { - --icon-size: 28px; - --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px - --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px - - list-style: none; - - &-label { - padding: calc( var( --spacing-base ) * 2 ); // 16px - } - - &-content { - padding: 0; - } - - &-list { - margin-left: var( --left-spacing ) ; - } - - &-truncate { - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - justify-content: flex-start; - } -} - -.popover-text { - width: 250px; - padding: calc( var( --spacing-base ) * 2 ); // 16px -} - -.navigation-dropdown { - &-button { - display: flex; - border: 1px solid var( --jp-gray-10 ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - background-color: var( --jp-white ); - justify-content: space-between; - align-items: center; - width: 100%; - } - - &-label { - display: flex; - justify-content: flex-start; - } - - &-icon { - margin-right: var( --spacing-base ); // 8px - } -} diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js deleted file mode 100644 index 2972dac06b572..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -export const NavigationContext = React.createContext(); - -const useMenuNavigation = ( { selected, onSelect } ) => { - const [ items, setItems ] = useState( [] ); - const [ refs, setRef ] = useState( [] ); - const [ focusedItem, setFocusedItem ] = useState(); - - const handleClickItem = id => { - onSelect( id ); - }; - - const handleFocusItem = id => { - setFocusedItem( id ); - }; - - const getPrevItem = ( current, last ) => { - const startMinusOne = current - 1; - const prevIndex = startMinusOne < 0 ? last : startMinusOne; - const prevItem = items[ prevIndex ]; - return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem; - }; - - const getNextItem = ( current, last ) => { - const startPlusOne = current + 1; - const nextIndex = startPlusOne > last ? 0 : startPlusOne; - const nextItem = items[ nextIndex ]; - return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem; - }; - - const handleKeyDownItem = input => { - const code = input?.code; - const current = items.findIndex( item => item?.id === selected ); - const lastIndex = items.length - 1; - - let nextId; - - if ( code === 'ArrowUp' ) { - const prevItem = getPrevItem( current, lastIndex ); - nextId = prevItem?.id; - } else if ( code === 'ArrowDown' ) { - const nextItem = getNextItem( current, lastIndex ); - nextId = nextItem?.id; - } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) { - nextId = focusedItem; - } - - if ( nextId ) { - const element = refs[ nextId ]; - element?.focus(); - onSelect( nextId ); - } - }; - - const registerRef = ( ref, id ) => { - setRef( allRefs => { - if ( ! allRefs[ id ] && ref ) { - return { ...allRefs, [ id ]: ref }; - } - return allRefs; - } ); - }; - - const registerItem = data => { - setItems( allItems => { - const newItems = [ ...allItems ]; - const id = data?.id; - const currentIdx = newItems.findIndex( item => item?.id === id ); - - if ( currentIdx >= 0 ) { - newItems[ currentIdx ] = data; - } else { - newItems.push( data ); - } - - return newItems; - } ); - }; - - return { - selectedItem: selected, - handleClickItem, - handleKeyDownItem, - handleFocusItem, - registerRef, - registerItem, - items, - }; -}; - -export default useMenuNavigation; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index c733ff1f0a08c..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - IconTooltip, - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
    - { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
    -); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - - - { createInterpolateElement( - __( - 'The fixer is taking longer than expected. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - - - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 8304942b206d4..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,202 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -.icon-tooltip { - max-height: 20px; - margin-left: calc( var( --spacing-base ) / 2 ); // 4px - - &__icon { - color: var( --jp-red ); - } - - &__content { - color: var( --jp-gray-70 ); - font-weight: 400; - line-height: 24px; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx deleted file mode 100644 index d1100d8ce6d5e..0000000000000 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ProtectCheck(): JSX.Element { - return ( - - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx deleted file mode 100644 index e626b6af066c7..0000000000000 --- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import usePlan from '../../hooks/use-plan'; -import ButtonGroup from '../button-group'; - -/** - * Navigation for scan sections. - * - * @return {React.Element} The React Component. - */ -export default function ScanNavigation() { - const navigate = useNavigate(); - const location = useLocation(); - const { hasPlan } = usePlan(); - - const viewingScanPage = location.pathname === '/scan'; - const viewingHistoryPage = location.pathname.includes( '/scan/history' ); - const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] ); - const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] ); - - if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) { - return null; - } - - return ( - <> - - - { __( 'Scanner', 'jetpack-protect' ) } - - - { __( 'History', 'jetpack-protect' ) } - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..45a8524e60b59 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,6 +1,7 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; import styles from './styles.module.scss'; @@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } ) return ( <>
    - +
    - { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
    - ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
    - - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
    - ) } - { fixedIn && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
    - ) } - { ! description &&
    { learnMoreButton }
    } -
    - ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
    - -
    - { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
    - { getTitle() } - { hasPlan && ( -
    - { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
    - ) } -
    - { hasPlan ? ( - <> -
    - -
    - - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
    -
    - { ! scanning && ( -
    - ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
    - - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
    - ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
    { filename }
    - - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
    - ) } - { ! description &&
    { learnMoreButton }
    } - { [ 'ignored', 'current' ].includes( status ) && ( -
    - { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
    - ) } -
    - ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
    - { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
    - ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index 2338d306e6780..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx index 1468fb40ba8cf..0dfd22468079e 100644 --- a/projects/plugins/protect/src/js/routes/firewall/index.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx @@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; -import ScanFooter from '../scan/scan-footer'; import FirewallAdminSectionHero from './firewall-admin-section-hero'; import FirewallFooter from './firewall-footer'; import styles from './styles.module.scss'; @@ -576,7 +575,7 @@ const FirewallPage = () => {
    - { wafSupported ? : } + { wafSupported && } ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 141c51cde284a..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previous %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previous threats', 'jetpack-protect' ) } - - - - { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } - - - - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
    -
    - { getTitle() } -
    - -
    -
    - -
    - ) : ( - <> -
    -
    - -
    -
    -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
    - - ) } - -
    - -
    -
    - ) } - -
    - ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index d30f3e0ac3344..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..c56ae3c747f3e 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,16 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import OnboardingPopover from '../../components/onboarding-popover'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; -import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; +import styles from './styles.module.scss'; /** * Scan Page @@ -19,23 +21,41 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); + const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: [ 'current' ], + operator: 'isAny', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,16 +69,33 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } - + + + +
    + +
    + { !! status && ! isScanInProgress( status ) && ( + + ) } + { !! status && ! isScanInProgress( status ) && hasPlan && ( + + ) } + +
    +
    ); diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx index 0e85aa56d9289..c29af26bcb409 100644 --- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx +++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx @@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan'; const { siteSuffix } = window.jetpackProtectInitialState; -const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' ); -const scanResultsDescription = ( - - { __( - 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files', - 'jetpack-protect' - ) } - -); const UpgradeButton = props => { const { upgradePlan } = usePlan(); const { recordEvent } = useAnalyticsTracks(); @@ -27,11 +18,6 @@ const UpgradeButton = props => { }; export default [ - { - id: 'free-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, - }, { id: 'free-daily-scans', title: __( 'Daily automated scans', 'jetpack-protect' ), @@ -49,10 +35,41 @@ export default [ ), }, + { + id: 'paid-daily-and-manual-scans', + title: __( 'Daily & manual scanning', 'jetpack-protect' ), + description: ( + + { __( + 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', + 'jetpack-protect' + ) } + + ), + }, + { + id: 'free-scan-results', + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.', + 'jetpack-protect' + ) } + + ), + }, { id: 'paid-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.', + 'jetpack-protect' + ) } + + ), }, { id: 'paid-fix-all-threats', @@ -97,16 +114,4 @@ export default [ ), }, - { - id: 'paid-daily-and-manual-scans', - title: __( 'Daily & manual scanning', 'jetpack-protect' ), - description: ( - - { __( - 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', - 'jetpack-protect' - ) } - - ), - }, ]; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 9e1b9c102a037..db76bac1b15b0 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,33 +1,41 @@ import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); - const { hasPlan } = usePlan(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); - const { list } = useThreatsList(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const { setModal } = useModal(); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); + + const { globalStats } = useWafData(); + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); + + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); @@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => { // List of fixable threats that do not have a fix in progress const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; return ( threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) ); } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } const handleShowAutoFixersClick = threatList => { @@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => { ) : __( 'Most recent results', 'jetpack-protect' ) } - { ! hasPlan && ( - - ) } + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( @@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => { ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) ) - : sprintf( - /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No active %s', 'jetpack-protect' ), - hasPlan - ? __( 'threats', 'jetpack-protect' ) - : __( - 'vulnerabilities', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) - ) } + : __( "Don't worry about a thing", 'jetpack-protect' ) } <> - - { __( - 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', - 'jetpack-protect' - ) } - - { fixableList.length > 0 && ( + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } + + ) : ( <> - - { ! scanning && ( - -
    - -
    } secondary={ } diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 908e34f6e71d7..163fd23248aaa 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,12 +1,14 @@ -.subheading-text { - white-space: nowrap; +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.product-section, .info-section { - margin-top: calc( var( --spacing-base ) * 7 ); // 56px - margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px -} +.scan-results-container { + padding-left: 0; + padding-right: 0; + overflow: hidden; -.auto-fixers { - margin-top: calc( var( --spacing-base ) * 4 ); // 32px -} \ No newline at end of file + > * { + margin-left: calc( var( --spacing-base ) * -3 ); // -24px + margin-right: calc( var( --spacing-base ) * -3 ); // -24px + } +} diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..0c65dfec146a7 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -33,6 +33,24 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + /** + * Transpile @wordpress/dataviews in node_modules too. + * + * @see https://github.com/Automattic/jetpack/issues/39907 + */ + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress/dataviews/' ], + babelOpts: { + configFile: false, + plugins: [ + [ + require.resolve( '@automattic/babel-plugin-replace-textdomain' ), + { textdomain: 'jetpack-protect' }, + ], + ], + }, + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From 0a9047edfb0d1311d51a71e32c08942fa075b0a7 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:22:55 -0800 Subject: [PATCH 106/290] Components: Add ScanReport (#40419) --- .../changelog/components-add-scan-report | 4 + .../components/scan-report/constants.ts | 30 +++ .../components/scan-report/index.tsx | 197 ++++++++++++++++++ .../scan-report/stories/index.stories.tsx | 72 +++++++ .../components/scan-report/styles.module.scss | 21 ++ .../components/shield-icon/index.tsx | 14 +- .../shield-icon/stories/index.stories.tsx | 6 +- projects/js-packages/components/index.ts | 1 + .../scan/changelog/components-add-scan-report | 4 + .../js-packages/scan/src/types/threats.ts | 24 ++- 10 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 projects/js-packages/components/changelog/components-add-scan-report create mode 100644 projects/js-packages/components/components/scan-report/constants.ts create mode 100644 projects/js-packages/components/components/scan-report/index.tsx create mode 100644 projects/js-packages/components/components/scan-report/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/scan-report/styles.module.scss create mode 100644 projects/js-packages/scan/changelog/components-add-scan-report diff --git a/projects/js-packages/components/changelog/components-add-scan-report b/projects/js-packages/components/changelog/components-add-scan-report new file mode 100644 index 0000000000000..ba0fbd4cce025 --- /dev/null +++ b/projects/js-packages/components/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport component diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts new file mode 100644 index 0000000000000..436eed91c5701 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -0,0 +1,30 @@ +import { __ } from '@wordpress/i18n'; +import { + code as fileIcon, + color as themeIcon, + plugins as pluginIcon, + shield as shieldIcon, + wordpress as coreIcon, +} from '@wordpress/icons'; + +export const TYPES = [ + { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, + { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, + { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, + { value: 'files', label: __( 'Files', 'jetpack-components' ) }, +]; + +export const ICONS = { + plugins: pluginIcon, + themes: themeIcon, + core: coreIcon, + files: fileIcon, + default: shieldIcon, +}; + +export const FIELD_ICON = 'icon'; +export const FIELD_TYPE = 'type'; +export const FIELD_NAME = 'name'; +export const FIELD_STATUS = 'status'; +export const FIELD_UPDATE = 'update'; +export const FIELD_VERSION = 'version'; diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx new file mode 100644 index 0000000000000..14795376f7d95 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -0,0 +1,197 @@ +import { type ScanReportExtension } from '@automattic/jetpack-scan'; +import { Tooltip } from '@wordpress/components'; +import { + type SupportedLayouts, + type View, + type Field, + DataViews, + filterSortAndPaginate, +} from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; +import { Icon } from '@wordpress/icons'; +import { useCallback, useMemo, useState } from 'react'; +import ShieldIcon from '../shield-icon'; +import { + FIELD_NAME, + FIELD_VERSION, + FIELD_ICON, + FIELD_STATUS, + FIELD_TYPE, + TYPES, + ICONS, +} from './constants'; +import styles from './styles.module.scss'; + +/** + * DataViews component for displaying a scan report. + * + * @param {object} props - Component props. + * @param {Array} props.data - Scan report data. + * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * + * @return {JSX.Element} The ScanReport component. + */ +export default function ScanReport( { data, onChangeSelection } ): JSX.Element { + const baseView = { + search: '', + filters: [], + page: 1, + perPage: 20, + }; + + /** + * DataView default layouts. + * + * This property provides layout information about the view types that are active. If empty, enables all layout types (see “Layout Types”) with empty layout data. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view + */ + const defaultLayouts: SupportedLayouts = { + table: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_TYPE, FIELD_NAME, FIELD_VERSION ], + layout: { + primaryField: FIELD_STATUS, + }, + }, + list: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_VERSION ], + layout: { + primaryField: FIELD_NAME, + mediaField: FIELD_ICON, + }, + }, + }; + + /** + * DataView view object - configures how the dataset is visible to the user. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object + */ + const [ view, setView ] = useState< View >( { + type: 'table', + ...defaultLayouts.table, + } ); + + /** + * DataView fields - describes the visible items for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object + */ + const fields = useMemo( () => { + const iconHeight = 20; + const result: Field< ScanReportExtension >[] = [ + { + id: FIELD_STATUS, + label: __( 'Status', 'jetpack-components' ), + render( { item }: { item: ScanReportExtension } ) { + let variant: 'info' | 'warning' | 'success' = 'info'; + let text = __( + 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', + 'jetpack-components' + ); + + if ( item.checked ) { + if ( item.threats.length > 0 ) { + variant = 'warning'; + text = __( 'Threat detected.', 'jetpack-components' ); + } else { + variant = 'success'; + text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + } + } + + return ( + +
    + +
    +
    + ); + }, + }, + { + id: FIELD_TYPE, + label: __( 'Type', 'jetpack-components' ), + elements: TYPES, + }, + { + id: FIELD_NAME, + label: __( 'Name', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.name ? item.name : ''; + }, + }, + { + id: FIELD_VERSION, + label: __( 'Version', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.version ? item.version : ''; + }, + }, + ...( view.type === 'list' + ? [ + { + id: FIELD_ICON, + label: __( 'Icon', 'jetpack-components' ), + enableSorting: false, + enableHiding: false, + getValue( { item }: { item: ScanReportExtension } ) { + return ICONS[ item.type ] || ''; + }, + render( { item }: { item: ScanReportExtension } ) { + return ( +
    + +
    + ); + }, + }, + ] + : [] ), + ]; + + return result; + }, [ view ] ); + + /** + * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. + * + * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts + */ + const { data: processedData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, fields ); + }, [ data, view, fields ] ); + + /** + * Callback function to update the view state. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function + */ + const onChangeView = useCallback( ( newView: View ) => { + setView( newView ); + }, [] ); + + /** + * DataView getItemId function - returns the unique ID for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function + */ + const getItemId = useCallback( ( item: ScanReportExtension ) => item.id.toString(), [] ); + + return ( + + ); +} diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx new file mode 100644 index 0000000000000..63926908850de --- /dev/null +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -0,0 +1,72 @@ +import ScanReport from '..'; + +export default { + title: 'JS Packages/Components/Scan Report', + component: ScanReport, + parameters: { + backgrounds: { + default: 'light', + values: [ { name: 'light', value: 'white' } ], + }, + }, + decorators: [ + Story => ( +
    + +
    + ), + ], +}; + +export const Default = args => ; +Default.args = { + data: [ + { + id: 1, + name: 'WordPress', + slug: null, + version: '6.7.1', + threats: [], + checked: true, + type: 'core', + }, + { + id: 2, + name: 'Jetpack', + slug: 'jetpack/jetpack.php', + version: '14.1-a.7', + threats: [], + checked: false, + type: 'plugins', + }, + { + id: 3, + name: 'Twenty Fifteen', + slug: 'twentyfifteen', + version: '1.1', + threats: [ + { + id: 198352527, + signature: 'Vulnerable.WP.Extension', + description: 'Vulnerable WordPress extension', + severity: 3, + }, + ], + checked: true, + type: 'themes', + }, + { + id: 4, + threats: [ + { + id: 198352406, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, + ], + checked: true, + type: 'files', + }, + ], +}; diff --git a/projects/js-packages/components/components/scan-report/styles.module.scss b/projects/js-packages/components/components/scan-report/styles.module.scss new file mode 100644 index 0000000000000..d313d4cb8898a --- /dev/null +++ b/projects/js-packages/components/components/scan-report/styles.module.scss @@ -0,0 +1,21 @@ +@import '@wordpress/dataviews/build-style/style.css'; + +.threat__media { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #EDFFEE; + border-color: #EDFFEE; + + svg { + fill: var( --jp-black ); + } +} + +.tooltip { + max-width: 240px; + border-radius: 4px; + text-align: left; +} \ No newline at end of file diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx index fee9f4d70c463..b07b943b5e7fa 100644 --- a/projects/js-packages/components/components/shield-icon/index.tsx +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; const COLORS = { - error: '#D63638', - warning: '#F0B849', - success: '#069E08', default: '#1d2327', + info: '#A7AAAD', + success: '#069E08', + warning: '#F0B849', + error: '#D63638', }; /** @@ -32,11 +33,11 @@ export default function ShieldIcon( { }: { className?: string; contrast?: string; - fill?: 'default' | 'success' | 'warning' | 'error' | string; + fill?: 'default' | 'info' | 'success' | 'warning' | 'error' | string; height?: number; icon?: 'success' | 'error'; outline?: boolean; - variant: 'default' | 'success' | 'warning' | 'error'; + variant: 'default' | 'info' | 'success' | 'warning' | 'error'; } ): JSX.Element { const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; const iconFill = outline ? shieldFill : contrast; @@ -60,6 +61,9 @@ export default function ShieldIcon( { } fill={ shieldFill } /> + { 'info' === iconVariant && ( + + ) } { 'success' === iconVariant && ( {
    +
    + diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index 4b0f3612012e7..6df50ee7fdb61 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -48,6 +48,7 @@ export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; export { default as ShieldIcon } from './components/shield-icon'; +export { default as ScanReport } from './components/scan-report'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/js-packages/scan/changelog/components-add-scan-report b/projects/js-packages/scan/changelog/components-add-scan-report new file mode 100644 index 0000000000000..eeb9c55de4a28 --- /dev/null +++ b/projects/js-packages/scan/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates/adds scan types diff --git a/projects/js-packages/scan/src/types/threats.ts b/projects/js-packages/scan/src/types/threats.ts index 72428c209ee49..22f6b06477163 100644 --- a/projects/js-packages/scan/src/types/threats.ts +++ b/projects/js-packages/scan/src/types/threats.ts @@ -4,6 +4,23 @@ export type ThreatStatus = 'fixed' | 'ignored' | 'current'; export type ThreatFixType = 'replace' | 'delete' | 'update' | string; +export type ScanReportExtension = { + id: number; + checked: boolean; + slug?: string; + name?: string; + version?: string; + threats: Threat[]; + type: 'plugins' | 'themes' | 'core' | 'files'; +}; + +export type Extension = { + slug: string; + name: string; + version: string; + type: 'plugins' | 'themes' | 'core'; +}; + export type Threat = { /** The threat's unique ID. */ id: string | number; @@ -57,10 +74,5 @@ export type Threat = { diff?: string; /** The affected extension. */ - extension?: { - slug: string; - name: string; - version: string; - type: 'plugins' | 'themes' | 'core'; - }; + extension?: Extension; }; From aa9b8a3e0584f26fcf0c52ef54ec8d6deea5c819 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 6 Dec 2024 12:05:11 -0700 Subject: [PATCH 107/290] Fix type errors Protect: add HMR support Revert "Protect: add HMR support" This reverts commit 06497a05bb050c86e097b36038c8742af427388d. --- projects/js-packages/scan/src/utils/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 9e2e75bcd4d91..30a96cbd132d5 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,9 +21,9 @@ export const getThreatIcon = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return 'wordpress-alt'; - case 'plugin': + case 'plugins': return 'plugins'; - case 'theme': + case 'themes': return 'appearance'; case 'file': return 'media-code'; @@ -36,9 +36,9 @@ export const getThreatSubtitle = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); - case 'plugin': + case 'plugins': return __( 'Vulnerable Plugin', 'jetpack-scan' ); - case 'theme': + case 'themes': return __( 'Vulnerable Theme', 'jetpack-scan' ); case 'file': return __( 'File Threat', 'jetpack-scan' ); From 08d8de31d17b6696d8a2abad56561ef5ac23ff86 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 9 Dec 2024 14:38:56 -0700 Subject: [PATCH 108/290] Protect: Refactor AdminSectionHero (#40516) --- .../components/admin-section-hero/index.tsx | 96 +++++----- .../stories/index.stories.jsx | 16 +- .../admin-section-hero/styles.module.scss | 44 +++-- .../error-admin-section-hero/index.tsx | 30 ++- .../styles.module.scss | 6 +- .../firewall/firewall-admin-section-hero.tsx | 27 ++- .../js/routes/firewall/firewall-statcards.jsx | 2 +- .../src/js/routes/firewall/styles.module.scss | 31 ++- .../routes/scan/scan-admin-section-hero.tsx | 177 +++++++++--------- .../scan/scanning-admin-section-hero.tsx | 70 ++++--- .../src/js/routes/scan/styles.module.scss | 10 + 11 files changed, 278 insertions(+), 231 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ccf607698084..7638936db5139 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -2,67 +2,73 @@ import { AdminSectionHero as JetpackAdminSectionHero, H3, ShieldIcon, + Container, + Col, } from '@automattic/jetpack-components'; -import SeventyFiveLayout from '../seventy-five-layout'; +import clsx from 'clsx'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; -interface AdminSectionHeroProps { - main: React.ReactNode; - secondary?: React.ReactNode; - preserveSecondaryOnMobile?: boolean; - spacing?: number; -} - -interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { - children: React.ReactNode; - showIcon?: boolean; - variant?: 'default' | 'success' | 'error'; - outline?: boolean; - } >; - Subheading: React.FC< { children: React.ReactNode } >; -} - -const AdminSectionHero: AdminSectionHeroComponent = ( { - main, - secondary, - preserveSecondaryOnMobile = true, - spacing = 7, -} ) => { +const AdminSectionHero = ( { + children, + ...props +}: React.ComponentProps< typeof JetpackAdminSectionHero > ) => { return ( - + - + + +
    { children }
    + +
    ); }; -AdminSectionHero.Heading = ( { +AdminSectionHero.Main = ( { children, - variant = 'default', - showIcon = false, + className, + ...props }: { children: React.ReactNode; - variant?: 'default' | 'success' | 'error'; - showIcon?: boolean; + className?: string; + [ key: string ]: unknown; +} ) => { + return ( +
    + { children } +
    + ); +}; + +AdminSectionHero.Aside = ( { + children, + className, + ...props +}: React.ComponentProps< 'div' > & { + className?: string; } ) => { return ( -

    +
    { children } - { showIcon && ( +
    + ); +}; + +AdminSectionHero.Heading = ( { + children, + icon, + ...props +}: React.ComponentProps< typeof H3 > & { + icon?: 'default' | 'success' | 'error'; +} ) => { + return ( +

    + { children } + { !! icon && ( { - return
    { children }
    ; -}; - export default AdminSectionHero; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index ca2dfda7fc98e..59ed9086d6317 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -9,16 +9,16 @@ export default { export const Default = args => ; Default.args = { - main: ( + children: ( <> - - - { 'No threats found' } - - + + + { 'No threats found' } { 'Most recent results' } - + + + + ), - secondary: , }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index a414aa9216f5c..74cfe29aaaded 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -1,15 +1,39 @@ -.header-main { +.admin-section-hero { display: flex; flex-direction: column; - justify-content: center; - align-items: flex-start; + gap: calc( var( --spacing-base ) * 6 ); // 48px + + max-width: var(--max-container-width); + padding: calc( var( --spacing-base ) * 6 ) 0; // 48px 0 + margin: 0 auto; + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media ( min-width: 1100px ) { + flex-direction: row; + align-items: center; + gap: calc( var( --spacing-base ) * 3 ); // 24px + } } -.header-secondary { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; +.admin-section-hero__main { + flex: 2; +} + +.admin-section-hero__aside { + flex: 1; + flex-shrink: 0; + + @media ( min-width: 1200px ) { + display: flex; + justify-content: flex-end; + } } .heading-icon { @@ -17,10 +41,6 @@ margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } -.subheading { - width: fit-content; -} - .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 1a9bc87387fa9..536d8f50de7d1 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -1,6 +1,5 @@ -import { Text } from '@automattic/jetpack-components'; +import { ShieldIcon, Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; import styles from './styles.module.scss'; @@ -19,22 +18,17 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { displayErrorMessage += ' ' + __( 'Try again in a few minutes.', 'jetpack-protect' ); return ( - - -
    - - { __( 'An error occurred', 'jetpack-protect' ) } -
    -
    - - { displayErrorMessage } - - - } - preserveSecondaryOnMobile={ false } - /> + + + +
    + { __( 'An error occurred', 'jetpack-protect' ) } + +
    +
    + { displayErrorMessage } +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss index 6f0750abd02f8..1c89377d4b4b5 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss @@ -4,11 +4,7 @@ } .warning { - width: 54px; - height: 54px; - fill: var( --jp-red ); - margin-left: -8px; - margin-right: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px } .scan-navigation { diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 3f70a75509b76..837f649c67f16 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -5,6 +5,7 @@ import AdminSectionHero from '../../components/admin-section-hero'; import useWafData from '../../hooks/use-waf-data'; import FirewallStatCards from './firewall-statcards'; import FirewallSubheading from './firewall-subheading'; +import styles from './styles.module.scss'; const FirewallAdminSectionHero = () => { const { @@ -84,16 +85,22 @@ const FirewallAdminSectionHero = () => { }, [ status ] ); return ( - - - { heading } - { subheading } - - } - secondary={ wafSupported && } - /> + + + + { heading } + { subheading } + + { wafSupported && ( + + + + ) } + ); }; diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 15c80df763c18..1eebd67cb60d7 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -93,7 +93,7 @@ const FirewallStatCards = () => { ); return ( -
    +
    diff --git a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss index afcbc2ad69b30..9404db7b56f09 100644 --- a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss @@ -3,6 +3,10 @@ max-width: calc( 744px + ( var( --spacing-base ) * 6 ) ); // 744px + 48px (desired inner width + horizontal padding) } +.status { + margin-bottom: calc( var( --spacing-base ) * 2 ); // 16px +} + .toggle-section { display: flex; @@ -145,14 +149,10 @@ align-items: center; } -.stat-card-wrapper { +.stat-cards-wrapper { display: flex; - margin-left: auto; - flex-wrap: wrap; - - >:first-child { - margin-right: calc( var( --spacing-base ) * 3 ); // 24px - } + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 3 ); // 24px .disabled { opacity: 0.5; @@ -220,6 +220,23 @@ background-color: var( --jp-white-off ); } +@media ( max-width: 1200px ) { + .stat-cards-wrapper { + justify-content: flex-start; + } +} + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} + .standalone-mode, .share-data { display: flex; flex-direction: column; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index db76bac1b15b0..4257c585351eb 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -59,6 +59,28 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } + let heading = __( "Don't worry about a thing", 'jetpack-protect' ); + if ( numThreats > 0 ) { + if ( hasPlan ) { + heading = sprintf( + /* translators: %s: Total number of threats */ + _n( '%1$s active threat', '%1$s active threats', numThreats, 'jetpack-protect' ), + numThreats + ); + } else { + heading = sprintf( + /* translators: %s: Total number of vulnerabilities */ + _n( + '%1$s active vulnerability', + '%1$s active vulnerabilities', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + } + const handleShowAutoFixersClick = threatList => { return event => { event.preventDefault(); @@ -84,94 +106,79 @@ const ScanAdminSectionHero: React.FC = () => { } return ( - - - { lastCheckedLocalTimestamp - ? sprintf( - // translators: %s: date and time of the last scan - __( '%s results', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) - ) - : __( 'Most recent results', 'jetpack-protect' ) } + + + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS, g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + + 0 ? 'error' : 'success' }> + { heading } + + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } - - 0 ? 'error' : 'success' }> - { numThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s active %2$s', 'jetpack-protect' ), - numThreats, - hasPlan - ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) - : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) - ) - : __( "Don't worry about a thing", 'jetpack-protect' ) } - - - <> - { hasPlan ? ( - - { __( - "We actively review your site's files line-by-line to identify threats and vulnerabilities.", - 'jetpack-protect' - ) } - - ) : ( - <> - - { sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - + ) : ( + <> + + { sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted ) } - { fixableList.length > 0 && ( - <> -
    - -
    -
    + -
    - - } - /> + > + + + + ) } + { fixableList.length > 0 && ( + <> +
    + +
    +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx index 4db4449b60119..ac9e0137cd170 100644 --- a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx @@ -18,44 +18,38 @@ const ScanningAdminSectionHero: React.FC = () => { : totalVulnerabilities.toLocaleString(); return ( - - - { __( 'Your results will be ready soon', 'jetpack-protect' ) } - - - <> - { hasPlan && ( - - ) } - - { hasPlan - ? __( - "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", - 'jetpack-protect' - ) - : sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - } - secondary={ } - preserveSecondaryOnMobile={ false } - spacing={ 4 } - /> + + + + { __( 'Your results will be ready soon', 'jetpack-protect' ) } + + { hasPlan && ( + + ) } + + { hasPlan + ? __( + "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", + 'jetpack-protect' + ) + : sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted + ) } + + + + + + ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 163fd23248aaa..5806ca5353863 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,3 +1,7 @@ +.scanning-main { + max-width: 512px; +} + .auto-fixers { margin-top: calc( var( --spacing-base ) * 4 ); // 32px } @@ -12,3 +16,9 @@ margin-right: calc( var( --spacing-base ) * -3 ); // -24px } } + +.progress-animation { + @media (max-width: 1099px) { + display: none; + } +} From b33c95abd805c1b3797b3bd0058269303ad91bfa Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:04:08 -0800 Subject: [PATCH 109/290] Protect: Update Scan History extension types (#40548) --- .../protect/src/class-scan-history.php | 4 +++ .../js/routes/firewall/firewall-footer.jsx | 2 -- .../src/js/routes/firewall/styles.module.scss | 30 ------------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index 8ea1dec7156e7..23019ccd634ad 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -219,6 +219,10 @@ private static function normalize_api_data( $scan_data ) { } foreach ( $scan_data->threats as $source_threat ) { + if ( ! empty( $source_threat->extension ) && in_array( $source_threat->extension->type, array( 'plugin', 'theme' ), true ) ) { + $source_threat->extension->type .= 's'; + } + $history->threats[] = new Threat_Model( $source_threat ); } diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx index 0e28d7bae7c98..0c175b1cd651f 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx @@ -69,7 +69,6 @@ const ShareData = () => {
    { __( 'Share data with Jetpack', 'jetpack-protect' ) } { ) } /> :first-child { - margin-right: 0; - margin-bottom: var( --spacing-base ); // 8px - } - } - - .stat-card-icon { - margin-bottom: 0; - } -} - -.share-data-section { - display: flex; - - .share-data-toggle { - margin-top: calc( var( --spacing-base ) / 2 ); // 4px - margin-right: var( --spacing-base ); // 8px - } -} - .icon-tooltip { max-height: 20px; margin-left: calc( var( --spacing-base ) / 2 ); // 4px From e13aa74c9969d0f59086b23357aba4f667ca28a9 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:25:25 -0800 Subject: [PATCH 110/290] Protect: Add Home page (#40317) * Init project branch * Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller * Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller * Protect: de-emphasize cloud link by using link variant (#40211) * Protect: add ShieldIcon component * Protect: Add ShieldIcon Component (#40402) * Protect: Integrate ThreatsDataViews Component (#40076) * Components: Add ScanReport (#40419) * Fix type errors * Protect: add home page --------- Co-authored-by: Nate Weller Co-authored-by: Nate Weller Co-authored-by: Dean Kmyta --- .../add-hide-value-prop-to-stat-card | 4 + .../components/components/stat-card/index.tsx | 13 +- .../components/components/stat-card/types.ts | 5 + .../protect/changelog/add-protect-home | 4 + .../protect/src/class-jetpack-protect.php | 3 +- .../src/js/components/admin-page/index.jsx | 1 + .../src/js/components/pricing-table/index.jsx | 2 +- .../components/seventy-five-layout/index.tsx | 73 ----- .../seventy-five-layout/styles.module.scss | 13 - .../plugins/protect/src/js/hooks/use-plan.tsx | 2 +- projects/plugins/protect/src/js/index.tsx | 4 +- .../routes/home/home-admin-section-hero.tsx | 50 ++++ .../src/js/routes/home/home-statcards.jsx | 274 ++++++++++++++++++ .../protect/src/js/routes/home/index.jsx | 25 ++ .../src/js/routes/home/styles.module.scss | 69 +++++ 15 files changed, 449 insertions(+), 93 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card create mode 100644 projects/plugins/protect/changelog/add-protect-home delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss create mode 100644 projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx create mode 100644 projects/plugins/protect/src/js/routes/home/home-statcards.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/styles.module.scss diff --git a/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card new file mode 100644 index 0000000000000..0d4002c768dd8 --- /dev/null +++ b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Stat Card: add hideValue prop diff --git a/projects/js-packages/components/components/stat-card/index.tsx b/projects/js-packages/components/components/stat-card/index.tsx index b6854dc02f37e..222cafb44068e 100644 --- a/projects/js-packages/components/components/stat-card/index.tsx +++ b/projects/js-packages/components/components/stat-card/index.tsx @@ -18,7 +18,14 @@ import type React from 'react'; * @param {StatCardProps} props - Component props. * @return {React.ReactNode} - StatCard react component. */ -const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCardProps ) => { +const StatCard = ( { + className, + icon, + label, + value, + variant = 'square', + hideValue = false, +}: StatCardProps ) => { const formattedValue = numberFormat( value ); const compactValue = numberFormat( value, { notation: 'compact', @@ -33,12 +40,12 @@ const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCa { variant === 'square' ? ( - { compactValue } + { hideValue ? '-' : compactValue } ) : ( - { formattedValue } + { hideValue ? '-' : formattedValue } ) }
    diff --git a/projects/js-packages/components/components/stat-card/types.ts b/projects/js-packages/components/components/stat-card/types.ts index 4b0fd698e6774..8e1c0e99d6d60 100644 --- a/projects/js-packages/components/components/stat-card/types.ts +++ b/projects/js-packages/components/components/stat-card/types.ts @@ -25,4 +25,9 @@ export type StatCardProps = { * @default 'square' */ variant?: 'square' | 'horizontal'; + + /** + * Whether to hide the value. + */ + hideValue?: boolean; }; diff --git a/projects/plugins/protect/changelog/add-protect-home b/projects/plugins/protect/changelog/add-protect-home new file mode 100644 index 0000000000000..0bcfedb6fe8ac --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds a Home page and StatCards diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 293ccdaeb3ce7..492cded402990 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -457,8 +457,9 @@ public static function get_waf_stats() { } return array( - 'blockedRequests' => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false, + 'blockedRequests' => Waf_Stats::get_blocked_requests(), 'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(), + 'blockedLogins' => (int) get_option( 'jetpack_protect_blocked_attempts', 0 ), ); } } diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 68f9359a9bd81..5811238cd266e 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -63,6 +63,7 @@ const AdminPage = ( { children } ) => { { notice && } + { const getProtectFree = useCallback( async () => { recordEvent( 'jetpack_protect_connected_product_activated' ); await connectSiteMutation.mutateAsync(); - navigate( '/scan' ); + navigate( '/' ); }, [ connectSiteMutation, recordEvent, navigate ] ); const args = { diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx b/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx deleted file mode 100644 index 19ee4309e55a5..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Container, Col, useBreakpointMatch } from '@automattic/jetpack-components'; -import React from 'react'; - -// Define the props interface for the SeventyFiveLayout component -interface SeventyFiveLayoutProps { - spacing?: number; - gap?: number; - main: React.ReactNode; - mainClassName?: string; - secondary: React.ReactNode; - secondaryClassName?: string; - preserveSecondaryOnMobile?: boolean; - fluid?: boolean; -} - -/** - * SeventyFive layout meta component - * The component name references to - * the sections disposition of the layout. - * FiftyFifty, 75, thus 7|5 means the cols numbers - * for main and secondary sections respectively, - * in large lg viewport size. - * - * @param {object} props - Component props - * @param {number} props.spacing - Horizontal spacing - * @param {number} props.gap - Horizontal gap - * @param {React.ReactNode} props.main - Main section component - * @param {string} props.mainClassName - Main section class name - * @param {React.ReactNode} props.secondary - Secondary section component - * @param {string} props.secondaryClassName - Secondary section class name - * @param {boolean} props.preserveSecondaryOnMobile - Whether to show secondary section on mobile - * @param {boolean} props.fluid - Whether to use fluid layout - * @return {React.ReactNode} - React meta-component - */ -const SeventyFiveLayout: React.FC< SeventyFiveLayoutProps > = ( { - spacing = 0, - gap = 0, - main, - mainClassName, - secondary, - secondaryClassName, - preserveSecondaryOnMobile = false, - fluid, -} ) => { - // Ensure the correct typing for useBreakpointMatch - const [ isSmall, isLarge ] = useBreakpointMatch( [ 'sm', 'lg' ] ); - - /* - * By convention, secondary section is not shown when: - * - preserveSecondaryOnMobile is false - * - on mobile breakpoint (sm) - */ - const hideSecondarySection = ! preserveSecondaryOnMobile && isSmall; - - return ( - - { ! hideSecondarySection && ( - <> - - { main } - - { isLarge && } - - { secondary } - - - ) } - { hideSecondarySection && { main } } - - ); -}; - -export default SeventyFiveLayout; diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss b/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss deleted file mode 100644 index 5405c6e28a9b4..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -// seventy-five layout -// Handle large lg size from here, -// adding a gap on one column -// in between main and secondary sections. -@media ( min-width: 960px ) { - .main { - grid-column: 1 / span 6; - } - - .secondary { - grid-column: 8 / span 5; - } -} diff --git a/projects/plugins/protect/src/js/hooks/use-plan.tsx b/projects/plugins/protect/src/js/hooks/use-plan.tsx index b5ab18da01875..f5cd1d54943b9 100644 --- a/projects/plugins/protect/src/js/hooks/use-plan.tsx +++ b/projects/plugins/protect/src/js/hooks/use-plan.tsx @@ -48,7 +48,7 @@ export default function usePlan( { redirectUrl }: { redirectUrl?: string } = {} const { run: checkout } = useProductCheckoutWorkflow( { productSlug: JETPACK_SCAN_SLUG, - redirectUrl: redirectUrl || adminUrl, + redirectUrl: redirectUrl || adminUrl + '#/scan', siteProductAvailabilityHandler: API.checkPlan, useBlogIdSuffix: true, connectAfterCheckout: false, diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index 2b91f4b090b92..4438d5021a664 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -11,6 +11,7 @@ import { NoticeProvider } from './hooks/use-notices'; import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; +import HomeRoute from './routes/home'; import ScanRoute from './routes/scan'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -56,6 +57,7 @@ function render() { } /> + } /> } /> } /> - } /> + } /> diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx new file mode 100644 index 0000000000000..12d887e933f43 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -0,0 +1,50 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import AdminSectionHero from '../../components/admin-section-hero'; +import usePlan from '../../hooks/use-plan'; +import HomeStatCards from './home-statcards'; +import styles from './styles.module.scss'; + +const HomeAdminSectionHero: React.FC = () => { + const { hasPlan } = usePlan(); + const navigate = useNavigate(); + const handleScanReportClick = useCallback( () => { + navigate( '/scan' ); + }, [ navigate ] ); + + return ( + + + <> + + { __( 'Your site is safe with us', 'jetpack-protect' ) } + + + { hasPlan + ? __( + 'We stay ahead of security threats to keep your site protected.', + 'jetpack-protect' + ) + : __( + 'We stay ahead of security vulnerabilities to keep your site protected.', + 'jetpack-protect' + ) } + + + + + { } + + ); +}; + +export default HomeAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx new file mode 100644 index 0000000000000..2d1dc34cac147 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx @@ -0,0 +1,274 @@ +import { Text, useBreakpointMatch, StatCard, ShieldIcon } from '@automattic/jetpack-components'; +import { Spinner, Tooltip } from '@wordpress/components'; +import { dateI18n } from '@wordpress/date'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import usePlan from '../../hooks/use-plan'; +import useWafData from '../../hooks/use-waf-data'; +import styles from './styles.module.scss'; + +const IconWithLabel = ( { label, isSmall, icon } ) => ( + + { icon } + { ! isSmall && ( + + { label } + + ) } + +); + +const HomeStatCard = ( { text, args } ) => ( + +
    + +
    +
    +); + +const HomeStatCards = () => { + const ICON_HEIGHT = 20; + + const { hasPlan } = usePlan(); + const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); + + const { data: status } = useScanStatusQuery(); + const scanning = isScanInProgress( status ); + const numThreats = status.threats.length; + const scanError = status.error; + + let lastCheckedLocalTimestamp = null; + if ( status.lastChecked ) { + // Convert the lastChecked UTC date to a local timestamp + lastCheckedLocalTimestamp = dateI18n( + 'F jS g:i A', + new Date( status.lastChecked + ' UTC' ).getTime(), + false + ); + } + + const { + config: { bruteForceProtection: isBruteForceModuleEnabled }, + isEnabled: isWafModuleEnabled, + wafSupported, + stats, + } = useWafData(); + + const { + blockedRequests: { allTime: allTimeBlockedRequestsCount = 0 } = {}, + blockedLogins: allTimeBlockedLoginsCount = 0, + } = stats || {}; + + const variant = useMemo( () => ( isSmall ? 'horizontal' : 'square' ), [ isSmall ] ); + + const lastCheckedMessage = useMemo( () => { + if ( scanning ) { + return __( 'Your results will be ready soon.', 'jetpack-protect' ); + } + + if ( scanError ) { + return __( + 'Please check your connection or try scanning again in a few minutes.', + 'jetpack-protect' + ); + } + + if ( lastCheckedLocalTimestamp ) { + if ( numThreats > 0 ) { + if ( hasPlan ) { + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d threat.', + 'Last checked on %1$s: We found %2$d threats.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d vulnerability.', + 'Last checked on %1$s: We found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %s: date/time + __( 'Last checked on %s: Your site is secure.', 'jetpack-protect' ), + lastCheckedLocalTimestamp + ); + } + if ( hasPlan ) { + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %d threat.', + 'Last scan we found %d threats.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %2$d vulnerability.', + 'Last scan we found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + }, [ scanError, scanning, numThreats, lastCheckedLocalTimestamp, hasPlan ] ); + + const scanArgs = useMemo( () => { + let scanIcon; + if ( scanning ) { + scanIcon = ; + } else if ( scanError ) { + scanIcon = ; + } else { + scanIcon = ( + + ); + } + + let scanLabel; + if ( scanning ) { + scanLabel = __( 'One moment, please…', 'jetpack-protect' ); + } else if ( scanError ) { + scanLabel = __( 'An error occurred', 'jetpack-protect' ); + } else if ( hasPlan ) { + scanLabel = _n( 'Threat identified', 'Threats identified', numThreats, 'jetpack-protect' ); + } else { + scanLabel = _n( + 'Vulnerability identified', + 'Vulnerabilities identified', + numThreats, + 'jetpack-protect' + ); + } + + return { + variant, + icon: ( + + ), + label: { scanLabel }, + value: numThreats, + hideValue: !! ( scanError || scanning ), + }; + }, [ variant, scanning, ICON_HEIGHT, scanError, numThreats, hasPlan, isSmall ] ); + + const wafArgs = useMemo( + () => ( { + variant: variant, + className: isWafModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Firewall', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked requests', 'jetpack-protect' ) } + + ), + value: allTimeBlockedRequestsCount, + hideValue: ! isWafModuleEnabled, + } ), + [ variant, isWafModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedRequestsCount ] + ); + + const bruteForceArgs = useMemo( + () => ( { + variant: variant, + className: isBruteForceModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Brute force', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked login attempts', 'jetpack-protect' ) } + + ), + value: allTimeBlockedLoginsCount, + hideValue: ! isBruteForceModuleEnabled, + } ), + [ variant, isBruteForceModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedLoginsCount ] + ); + + return ( +
    + + { wafSupported && ( + + ) } + +
    + ); +}; + +export default HomeStatCards; diff --git a/projects/plugins/protect/src/js/routes/home/index.jsx b/projects/plugins/protect/src/js/routes/home/index.jsx new file mode 100644 index 0000000000000..718349caaac3f --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/index.jsx @@ -0,0 +1,25 @@ +import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import AdminPage from '../../components/admin-page'; +import HomeAdminSectionHero from './home-admin-section-hero'; + +/** + * Home Page + * + * The entry point for the Home page. + * + * @return {Component} The root component for the scan page. + */ +const HomePage = () => { + return ( + + + + + { /* TODO: Add ScanReport component here */ } + + + + ); +}; + +export default HomePage; diff --git a/projects/plugins/protect/src/js/routes/home/styles.module.scss b/projects/plugins/protect/src/js/routes/home/styles.module.scss new file mode 100644 index 0000000000000..b99bead52dbdb --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/styles.module.scss @@ -0,0 +1,69 @@ +.product-section, .info-section { + margin-top: calc( var( --spacing-base ) * 7 ); // 56px + margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px +} + +.view-scan-report { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px +} + +.stat-cards-wrapper { + display: flex; + justify-content: flex-start; + + > *:not( last-child ) { + margin-right: calc( var( --spacing-base ) * 3 ); // 24px + } + + .disabled { + opacity: 0.5; + } +} + +.stat-card-icon { + width: 100%; + margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px + display: flex; + align-items: center; + gap: 8px; + + svg { + margin: 0; + } + + .active { + fill: var( --jp-green-40 ); + } + + .warning { + fill: var( --jp-yellow-40 ); + } + + .disabled { + fill: var( --jp-gray-40 ); + } + + &-label { + color: var( --jp-black ); + white-space: nowrap; + } +} + +.stat-card-tooltip { + margin-top: 8px; + max-width: 240px; + border-radius: 4px; + text-align: left; +} + + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} \ No newline at end of file From 322819d1756fe6ff5702635d347ed14fdb43d298 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:17:14 -0800 Subject: [PATCH 111/290] Protect: Integrate ScanReport (#40420) --- .../components/scan-report/constants.ts | 6 +++ .../components/scan-report/index.tsx | 48 +++++++++++++++++-- .../scan-report/stories/index.stories.tsx | 7 +++ .../threats-data-views/constants.ts | 1 + .../components/threats-data-views/index.tsx | 13 +---- .../update-protect-add-scan-report-to-home | 4 ++ .../firewall/firewall-admin-section-hero.tsx | 4 +- .../js/routes/firewall/firewall-statcards.jsx | 22 ++++----- .../routes/home/home-admin-section-hero.tsx | 3 +- .../protect/src/js/routes/home/index.jsx | 26 ++++++++-- .../src/js/routes/home/styles.module.scss | 13 +++-- 11 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-add-scan-report-to-home diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts index 436eed91c5701..6a10d008b876f 100644 --- a/projects/js-packages/components/components/scan-report/constants.ts +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -7,6 +7,12 @@ import { wordpress as coreIcon, } from '@wordpress/icons'; +export const STATUS_TYPES = [ + { value: 'checked', label: __( 'Checked', 'jetpack-components' ) }, + { value: 'unchecked', label: __( 'Unchecked', 'jetpack-components' ) }, + { value: 'threat', label: __( 'Threat', 'jetpack-components' ) }, +]; + export const TYPES = [ { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx index 14795376f7d95..4600ecf98d9db 100644 --- a/projects/js-packages/components/components/scan-report/index.tsx +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -7,7 +7,7 @@ import { DataViews, filterSortAndPaginate, } from '@wordpress/dataviews'; -import { __ } from '@wordpress/i18n'; +import { __, _n } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; import ShieldIcon from '../shield-icon'; @@ -17,6 +17,7 @@ import { FIELD_ICON, FIELD_STATUS, FIELD_TYPE, + STATUS_TYPES, TYPES, ICONS, } from './constants'; @@ -26,12 +27,13 @@ import styles from './styles.module.scss'; * DataViews component for displaying a scan report. * * @param {object} props - Component props. + * @param {string} props.dataSource - Data source. * @param {Array} props.data - Scan report data. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. * * @return {JSX.Element} The ScanReport component. */ -export default function ScanReport( { data, onChangeSelection } ): JSX.Element { +export default function ScanReport( { dataSource, data, onChangeSelection } ): JSX.Element { const baseView = { search: '', filters: [], @@ -84,8 +86,19 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { const result: Field< ScanReportExtension >[] = [ { id: FIELD_STATUS, + elements: STATUS_TYPES, label: __( 'Status', 'jetpack-components' ), + getValue( { item } ) { + if ( item.checked ) { + if ( item.threats.length > 0 ) { + return 'threat'; + } + return 'checked'; + } + return 'unchecked'; + }, render( { item }: { item: ScanReportExtension } ) { + const scanApi = 'scan_api' === dataSource; let variant: 'info' | 'warning' | 'success' = 'info'; let text = __( 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', @@ -95,10 +108,34 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { if ( item.checked ) { if ( item.threats.length > 0 ) { variant = 'warning'; - text = __( 'Threat detected.', 'jetpack-components' ); + text = _n( + 'Vulnerability detected.', + 'Vulnerabilities detected.', + item.threats.length, + 'jetpack-components' + ); + + if ( scanApi ) { + text = _n( + 'Threat detected.', + 'Threats detected.', + item.threats.length, + 'jetpack-components' + ); + } } else { variant = 'success'; - text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + text = __( + 'No known vulnerabilities found that affect this version.', + 'jetpack-components' + ); + + if ( scanApi ) { + text = __( + 'No known threats found that affect this version.', + 'jetpack-components' + ); + } } } @@ -127,6 +164,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { { id: FIELD_VERSION, label: __( 'Version', 'jetpack-components' ), + enableSorting: false, enableGlobalSearch: true, getValue( { item }: { item: ScanReportExtension } ) { return item.version ? item.version : ''; @@ -155,7 +193,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { ]; return result; - }, [ view ] ); + }, [ view, dataSource ] ); /** * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx index 63926908850de..eebcbc428fb39 100644 --- a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -20,6 +20,7 @@ export default { export const Default = args => ; Default.args = { + dataSource: 'scan_api', data: [ { id: 1, @@ -64,6 +65,12 @@ Default.args = { title: 'Malicious code found in file: jptt_eicar.php', severity: 1, }, + { + id: 198352407, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, ], checked: true, type: 'files', diff --git a/projects/js-packages/components/components/threats-data-views/constants.ts b/projects/js-packages/components/components/threats-data-views/constants.ts index 59b79a7618453..84c7c5cf7b0cd 100644 --- a/projects/js-packages/components/components/threats-data-views/constants.ts +++ b/projects/js-packages/components/components/threats-data-views/constants.ts @@ -19,6 +19,7 @@ export const THREAT_TYPES = [ { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'file', label: __( 'File', 'jetpack-components' ) }, + { value: '', label: __( 'Unknown', 'jetpack-components' ) }, ]; export const THREAT_ICONS = { diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 6af7c028f4c3a..0d81b8995060e 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -280,18 +280,7 @@ export default function ThreatsDataViews( { label: __( 'Type', 'jetpack-components' ), elements: THREAT_TYPES, getValue( { item }: { item: Threat } ) { - switch ( getThreatType( item ) ) { - case 'core': - return __( 'WordPress', 'jetpack-components' ); - case 'plugins': - return __( 'Plugin', 'jetpack-components' ); - case 'themes': - return __( 'Theme', 'jetpack-components' ); - case 'file': - return __( 'File', 'jetpack-components' ); - default: - return __( 'Unknown', 'jetpack-components' ); - } + return getThreatType( item ) ?? ''; }, }, { diff --git a/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home new file mode 100644 index 0000000000000..0478ae51501b8 --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport to HomeRoute diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 837f649c67f16..c302f93dd8863 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -88,12 +88,12 @@ const FirewallAdminSectionHero = () => { { heading } - { subheading } + { subheading } { wafSupported && ( diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 1eebd67cb60d7..7b1fd7cbbbede 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -1,13 +1,11 @@ -import { Text, useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; +import { useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; import { __, sprintf } from '@wordpress/i18n'; import { Icon, shield, chartBar } from '@wordpress/icons'; import { useCallback, useMemo } from 'react'; -import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; import styles from './styles.module.scss'; const FirewallStatCards = () => { - const { hasPlan } = usePlan(); const { config: { bruteForceProtection: isBruteForceModuleEnabled }, isEnabled: isWafModuleEnabled, @@ -22,26 +20,22 @@ const FirewallStatCards = () => { const { currentDay: currentDayBlockCount, thirtyDays: thirtyDayBlockCounts } = stats ? stats.blockedRequests : { currentDay: 0, thirtyDays: 0 }; - const isFeatureDisabled = ! isSupportedWafFeatureEnabled || ! hasPlan; const defaultArgs = useMemo( () => ( { - className: isFeatureDisabled ? styles.disabled : styles.active, + className: ! isSupportedWafFeatureEnabled ? styles.disabled : styles.active, variant: isSmall ? 'horizontal' : 'square', } ), - [ isFeatureDisabled, isSmall ] + [ isSupportedWafFeatureEnabled, isSmall ] ); const StatCardIcon = useCallback( ( { icon } ) => ( - { ! isSmall && ! hasPlan && ( - { __( 'Paid feature', 'jetpack-protect' ) } - ) } ), - [ isSmall, hasPlan ] + [] ); const StatCardLabel = useCallback( @@ -77,9 +71,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : currentDayBlockCount, + value: ! isSupportedWafFeatureEnabled ? 0 : currentDayBlockCount, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, currentDayBlockCount ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, currentDayBlockCount ] ); const thirtyDaysArgs = useMemo( @@ -87,9 +81,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : thirtyDayBlockCounts, + value: ! isSupportedWafFeatureEnabled ? 0 : thirtyDayBlockCounts, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, thirtyDayBlockCounts ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, thirtyDayBlockCounts ] ); return ( diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx index 12d887e933f43..d695f05eea9cc 100644 --- a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -29,7 +29,8 @@ const HomeAdminSectionHero: React.FC = () => { ) : __( 'We stay ahead of security vulnerabilities to keep your site protected.', - 'jetpack-protect' + 'jetpack-protect', + /* dummy arg to avoid bad minification */ 0 ) }
    + +
    + ) } +

    + } > { notice && } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index e70d2cdb076c7..adf7dc594b907 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From 405dee73419438a9ef23c38a76488675befe2b24 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 114/290] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 9c8f30b7b8067..4aa517f5f120b 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import AdminSectionHero from '../../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } + { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..1c5cc6cac49b9 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; +import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useFixers from '../../hooks/use-fixers'; +import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return list.filter( threat => { + const threatId = parseInt( threat.id ); + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From 93dbb6a97fbd185abb475cb5f651332fa3da8216 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 115/290] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
    - diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adf7dc594b907..adc0cee561ba5 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From 9c4161ecc0d08774057bfc8b27c567c093e90d21 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 29 Nov 2024 21:00:37 -0700 Subject: [PATCH 116/290] Protect: add ShieldIcon component --- .../components/admin-section-hero/index.tsx | 21 ++- .../stories/index.stories.jsx | 4 +- .../src/js/components/shield-icon/index.tsx | 165 ++++++++++++++++++ .../shield-icon/stories/index.stories.tsx | 50 ++++++ 4 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ed83bebc8638..758c8c21e0193 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,9 +1,6 @@ -import { - AdminSectionHero as JetpackAdminSectionHero, - H3, - getIconBySlug, -} from '@automattic/jetpack-components'; +import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; +import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -15,7 +12,7 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean } >; + Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -44,17 +41,23 @@ const AdminSectionHero: AdminSectionHeroComponent = ( { AdminSectionHero.Heading = ( { children, + variant = 'default', showIcon = false, }: { children: React.ReactNode; + variant?: 'default' | 'success' | 'error'; showIcon?: boolean; } ) => { - const Icon = getIconBySlug( 'protect' ); - return (

    { children } - { showIcon && } + { showIcon && ( + + ) }

    ); }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index 7d5b4f8066c93..ca2dfda7fc98e 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -12,7 +12,9 @@ Default.args = { main: ( <> - { 'No threats found' } + + { 'No threats found' } + { 'Most recent results' } diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..3bf7f479f0051 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/index.tsx @@ -0,0 +1,165 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.variant - Icon variant. + * @param {string} props.fill - Icon fill color. + * @param {string} props.className - Additional class names. + * @param {number} props.height - Icon height. + * @return {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ShieldIcon( { + variant = 'default', + height = 32, + className, + fill, +}: { + variant: + | 'default' + | 'success' + | 'error' + | 'default-outline' + | 'success-outline' + | 'error-outline'; + className?: string; + height?: number; + fill?: string; +} ): JSX.Element { + if ( 'error-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'error' === variant ) { + return ( + + + + + ); + } + + if ( 'success-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'success' === variant ) { + return ( + + + + + ); + } + + if ( 'default-outline' === variant ) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..d10365f4b0834 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ShieldIcon from '../index'; + +export default { + title: 'Plugins/Protect/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
    + +
    + ), + ], + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ + 'default', + 'success', + 'error', + 'default-outline', + 'success-outline', + 'error-outline', + ], + }, + fill: { + control: 'color', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'default', +}; + +export const SuccessVariant = args => ; +SuccessVariant.args = { + variant: 'success', +}; + +export const ErrorVariant = args => ; +ErrorVariant.args = { + variant: 'error', +}; From b50d50dbbdfcc6a43cfbb6b5c86dd5e8bf3b86a0 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 4 Dec 2024 20:26:54 -0700 Subject: [PATCH 117/290] Protect: Add ShieldIcon Component (#40402) --- .../components/changelog/add-shield-icon | 4 + .../components/shield-icon/index.tsx | 79 +++++++++ .../shield-icon/stories/index.stories.tsx | 54 ++++++ projects/js-packages/components/index.ts | 1 + .../protect/changelog/refactor-alert-icon | 5 + .../components/admin-section-hero/index.tsx | 20 ++- .../admin-section-hero/styles.module.scss | 4 +- .../src/js/components/alert-icon/index.jsx | 74 -------- .../alert-icon/stories/index.stories.jsx | 17 -- .../components/alert-icon/styles.module.scss | 11 -- .../src/js/components/shield-icon/index.tsx | 165 ------------------ .../shield-icon/stories/index.stories.tsx | 50 ------ .../history/history-admin-section-hero.tsx | 2 +- .../routes/scan/scan-admin-section-hero.tsx | 2 +- 14 files changed, 162 insertions(+), 326 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-shield-icon create mode 100644 projects/js-packages/components/components/shield-icon/index.tsx create mode 100644 projects/js-packages/components/components/shield-icon/stories/index.stories.tsx create mode 100644 projects/plugins/protect/changelog/refactor-alert-icon delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/js-packages/components/changelog/add-shield-icon b/projects/js-packages/components/changelog/add-shield-icon new file mode 100644 index 0000000000000..5c6cc27eeb809 --- /dev/null +++ b/projects/js-packages/components/changelog/add-shield-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add ShieldIcon component diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..fee9f4d70c463 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +const COLORS = { + error: '#D63638', + warning: '#F0B849', + success: '#069E08', + default: '#1d2327', +}; + +/** + * Protect Shield SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.className - Additional class names. + * @param {string} props.contrast - Icon contrast color. Overrides variant. + * @param {string} props.fill - Icon fill color (default, success, warning, error, or a custom color code string). Overrides variant. + * @param {number} props.height - Icon height (px). Width is calculated based on height. + * @param {string} props.icon - Icon variant (success, error). Overrides variant. + * @param {boolean} props.outline - When enabled, the icon will use an outline style. + * @param {string} props.variant - Icon variant (default, success, error). + * + * @return {React.ReactElement} Protect Shield SVG Icon + */ +export default function ShieldIcon( { + className, + contrast = '#fff', + fill, + height = 32, + icon, + outline = false, + variant = 'default', +}: { + className?: string; + contrast?: string; + fill?: 'default' | 'success' | 'warning' | 'error' | string; + height?: number; + icon?: 'success' | 'error'; + outline?: boolean; + variant: 'default' | 'success' | 'warning' | 'error'; +} ): JSX.Element { + const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; + const iconFill = outline ? shieldFill : contrast; + const iconVariant = icon || variant; + + return ( + + + { 'success' === iconVariant && ( + + ) } + { [ 'warning', 'error' ].includes( iconVariant ) && ( + + ) } + + ); +} diff --git a/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..b5a16d4da4075 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,54 @@ +import ShieldIcon from '../index'; + +export default { + title: 'JS Packages/Components/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ 'default', 'success', 'warning', 'error' ], + }, + icon: { + control: { + type: 'select', + }, + options: [ 'success', 'error' ], + }, + fill: { + control: 'color', + }, + outline: { + control: 'boolean', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'success', + outline: false, +}; + +export const Variants = () => { + return ( +
    +
    + + + + +
    +
    + + + + +
    +
    + ); +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index eb90df97ad5fe..4b0f3612012e7 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ShieldIcon } from './components/shield-icon'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/plugins/protect/changelog/refactor-alert-icon b/projects/plugins/protect/changelog/refactor-alert-icon new file mode 100644 index 0000000000000..46b4c247b1b9f --- /dev/null +++ b/projects/plugins/protect/changelog/refactor-alert-icon @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Refactored icon component code. + + diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 758c8c21e0193..5ccf607698084 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,6 +1,9 @@ -import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; +import { + AdminSectionHero as JetpackAdminSectionHero, + H3, + ShieldIcon, +} from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; -import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -12,7 +15,12 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; + Heading: React.FC< { + children: React.ReactNode; + showIcon?: boolean; + variant?: 'default' | 'success' | 'error'; + outline?: boolean; + } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -53,8 +61,10 @@ AdminSectionHero.Heading = ( { { children } { showIcon && ( ) } diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index 5881bcd910045..a414aa9216f5c 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -13,7 +13,7 @@ } .heading-icon { - margin-left: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } @@ -23,4 +23,4 @@ .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px -} \ No newline at end of file +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
    - - - - - - - - - - - - - - - - - - - -
    - ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx deleted file mode 100644 index 47b2ee32d4b51..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import AlertIcon from '../index.jsx'; - -export default { - title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, -}; - -const FooterTemplate = args => ; -export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx deleted file mode 100644 index 3bf7f479f0051..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @param {object} props - Component props. - * @param {string} props.variant - Icon variant. - * @param {string} props.fill - Icon fill color. - * @param {string} props.className - Additional class names. - * @param {number} props.height - Icon height. - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ShieldIcon( { - variant = 'default', - height = 32, - className, - fill, -}: { - variant: - | 'default' - | 'success' - | 'error' - | 'default-outline' - | 'success-outline' - | 'error-outline'; - className?: string; - height?: number; - fill?: string; -} ): JSX.Element { - if ( 'error-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'error' === variant ) { - return ( - - - - - ); - } - - if ( 'success-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'success' === variant ) { - return ( - - - - - ); - } - - if ( 'default-outline' === variant ) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx deleted file mode 100644 index d10365f4b0834..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ShieldIcon from '../index'; - -export default { - title: 'Plugins/Protect/Sheild Icon', - component: ShieldIcon, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], - argTypes: { - variant: { - control: { - type: 'select', - }, - options: [ - 'default', - 'success', - 'error', - 'default-outline', - 'success-outline', - 'error-outline', - ], - }, - fill: { - control: 'color', - }, - }, -}; - -export const Default = args => ; -Default.args = { - variant: 'default', -}; - -export const SuccessVariant = args => ; -SuccessVariant.args = { - variant: 'success', -}; - -export const ErrorVariant = args => ; -ErrorVariant.args = { - variant: 'error', -}; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 4aa517f5f120b..141c51cde284a 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -60,7 +60,7 @@ const HistoryAdminSectionHero: React.FC = () => { __( 'Most recent results', 'jetpack-protect' ) ) } - + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 1c5cc6cac49b9..9e1b9c102a037 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -95,7 +95,7 @@ const ScanAdminSectionHero: React.FC = () => { anchor={ dailyScansPopoverAnchor } /> ) } - + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ From 91e17a5be66b1032a752d9425a35215d0ec51b5c Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 5 Dec 2024 10:50:27 -0700 Subject: [PATCH 118/290] Protect: Integrate ThreatsDataViews Component (#40076) --- pnpm-lock.yaml | 3 + .../add-threat-subtitle-and-icon-utils | 4 + projects/js-packages/scan/src/utils/index.ts | 32 +- .../protect/changelog/add-threats-data-views | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 30 +- projects/plugins/protect/src/js/api.ts | 3 +- .../src/js/components/admin-page/index.jsx | 12 +- .../components/admin-page/styles.module.scss | 11 + .../error-admin-section-hero/index.tsx | 4 - .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 14 +- .../src/js/components/navigation/badge.jsx | 101 ------ .../src/js/components/navigation/group.jsx | 51 --- .../src/js/components/navigation/index.jsx | 73 ----- .../src/js/components/navigation/item.jsx | 85 ----- .../src/js/components/navigation/label.jsx | 24 -- .../components/navigation/styles.module.scss | 142 --------- .../navigation/use-menu-navigation.js | 92 ------ .../js/components/paid-accordion/index.jsx | 192 ----------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 202 ------------ .../src/js/components/pricing-table/index.jsx | 4 +- .../components/protect-check-icon/index.tsx | 25 -- .../js/components/scan-navigation/index.jsx | 44 --- .../js/components/threat-fix-header/index.jsx | 7 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 -------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 -------- .../js/components/threats-list/pagination.jsx | 142 --------- .../js/components/threats-list/paid-list.jsx | 253 --------------- .../threats-list/styles.module.scss | 129 -------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/hooks/use-protect-data/index.ts | 173 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../protect/src/js/routes/firewall/index.jsx | 3 +- .../history/history-admin-section-hero.tsx | 84 ----- .../src/js/routes/scan/history/index.jsx | 301 ------------------ .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 37 --- .../protect/src/js/routes/scan/index.jsx | 79 +++-- .../src/js/routes/scan/onboarding-steps.jsx | 61 ++-- .../routes/scan/scan-admin-section-hero.tsx | 138 ++++---- .../src/js/routes/scan/scan-footer.jsx | 143 --------- .../js/routes/scan/scan-results-data-view.tsx | 56 ++++ .../scan/scanning-admin-section-hero.tsx | 25 +- .../src/js/routes/scan/styles.module.scss | 20 +- projects/plugins/protect/webpack.config.js | 18 ++ 53 files changed, 363 insertions(+), 3668 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils create mode 100644 projects/plugins/protect/changelog/add-threats-data-views delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/badge.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/group.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/item.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/label.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/scan-navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/routes/scan/scan-footer.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eba1d19846d96..eca957986d25e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4231,6 +4231,9 @@ importers: specifier: 6.2.2 version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@automattic/babel-plugin-replace-textdomain': + specifier: workspace:* + version: link:../../js-packages/babel-plugin-replace-textdomain '@automattic/jetpack-webpack-config': specifier: workspace:* version: link:../../js-packages/webpack-config diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils new file mode 100644 index 0000000000000..ad8fa81458278 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add utilities for generating threat subtitle and icons diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 945cd0ecb7fa8..9e2e75bcd4d91 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => { return null; }; +export const getThreatIcon = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return 'wordpress-alt'; + case 'plugin': + return 'plugins'; + case 'theme': + return 'appearance'; + case 'file': + return 'media-code'; + default: + return 'shield-alt'; + } +}; + +export const getThreatSubtitle = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); + case 'plugin': + return __( 'Vulnerable Plugin', 'jetpack-scan' ); + case 'theme': + return __( 'Vulnerable Theme', 'jetpack-scan' ); + case 'file': + return __( 'File Threat', 'jetpack-scan' ); + default: + return __( 'Threat', 'jetpack-scan' ); + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); @@ -123,7 +153,7 @@ export const getFixerDescription = ( threat: Threat ) => { } break; case 'update': - if ( threat.fixedIn && threat.extension.name ) { + if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ __( 'Update %1$s to version %2$s', 'jetpack-scan' ), diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views new file mode 100644 index 0000000000000..e15bd6a461a71 --- /dev/null +++ b/projects/plugins/protect/changelog/add-threats-data-views @@ -0,0 +1,5 @@ +Significance: minor +Type: changed + +Added DataViews component for viewing scan results. + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index e408077e068bf..1751df7e06f5a 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -49,6 +49,7 @@ "react-router-dom": "6.2.2" }, "devDependencies": { + "@automattic/babel-plugin-replace-textdomain": "workspace:*", "@automattic/jetpack-webpack-config": "workspace:*", "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index bd034c375caf9..8ea1dec7156e7 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -207,43 +207,19 @@ public static function fetch_from_api() { * Normalize API Data * Formats the payload from the Scan API into an instance of History_Model. * - * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility. - * * @param object $scan_data The data returned by the scan API. * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..97d11fd5c0f2b 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,6 +1,7 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { WafStatus } from './types/waf'; const API = { getWaf: (): Promise< WafStatus > => diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 2d023560517f3..68f9359a9bd81 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useNotices from '../../hooks/use-notices'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; import ScanButton from '../scan-button'; @@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => { const { isRegistered } = useConnection(); const { isSeen: wafSeen } = useWafData(); const navigate = useNavigate(); - const { - counts: { - current: { threats: numThreats }, - }, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); const location = useLocation(); const { hasPlan } = usePlan(); @@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => { link="/scan" label={ - { numThreats > 0 + { status.threats.length > 0 ? sprintf( // translators: %d is the number of threats found. __( 'Scan (%d)', 'jetpack-protect' ), - numThreats + status.threats.length ) : __( 'Scan', 'jetpack-protect' ) } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adc0cee561ba5..da2e9510cd7d9 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -5,10 +5,21 @@ .header { display: flex; justify-content: space-between; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px + align-items: center; + + @media ( min-width: 600px ) { + flex-direction: row; + } &__scan_buttons { display: flex; gap: calc( var( --spacing-base ) * 3 ); // 24px + + @media ( min-width: 600px ) { + flex-direction: row; + } } } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 5214531dcf362..1a9bc87387fa9 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; -import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; interface ErrorAdminSectionHeroProps { @@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { { displayErrorMessage } -
    - -
    } preserveSecondaryOnMobile={ false } diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
    - +
    diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx index 7e8113b6f38ab..0788eb8bd7a41 100644 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx @@ -1,16 +1,18 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx deleted file mode 100644 index 93ebecf7235ef..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/badge.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, check, info } from '@wordpress/icons'; -import PropTypes from 'prop-types'; -import React, { useState, useCallback, useMemo } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import styles from './styles.module.scss'; - -/** - * Gets the Badge element - * - * @param {number} count - The number of threats found for this item. - * @param {boolean} checked - Whether this item was checked for threats yet. - * @return {object} The badge element - */ -const getBadgeElement = ( count, checked ) => { - if ( ! checked ) { - return { - popoverText: __( - 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', - 'jetpack-protect' - ), - badgeElement: ( - - ), - }; - } - - if ( count === 0 ) { - return { - popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ), - badgeElement: ( - - ), - }; - } - - return { - popoverText: null, - badgeElement: ( - - { count } - - ), - }; -}; - -const ItemBadge = ( { count, checked } ) => { - const { data: status } = useScanStatusQuery(); - - const { popoverText, badgeElement } = getBadgeElement( count, checked ); - const [ showPopover, setShowPopover ] = useState( false ); - - const inProgress = useMemo( () => isScanInProgress( status ), [ status ] ); - - const handleEnter = useCallback( () => { - if ( inProgress ) { - return; - } - - setShowPopover( true ); - }, [ inProgress ] ); - - const handleOut = useCallback( () => { - setShowPopover( false ); - }, [] ); - - return ( -
    - { ! inProgress ? badgeElement : } - { showPopover && ( - - - { popoverText } - - - ) } -
    - ); -}; - -ItemBadge.propTypes = { - /* The number of threats found for this item */ - count: PropTypes.number, - /* Whether this item was checked for threats yet */ - checked: PropTypes.bool, -}; - -export default ItemBadge; diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx deleted file mode 100644 index 9352ae5c63d67..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/group.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useState, useCallback, useContext } from 'react'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const MAX_ITEMS = 8; - -const NavigationGroup = ( { icon, label, children } ) => { - const [ collapsed, setCollapsed ] = useState( true ); - const { mode } = useContext( NavigationContext ); - const needsTruncate = - Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list'; - const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children; - const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0; - - const handleCollapsedToggle = useCallback( () => { - setCollapsed( current => ! current ); - }, [] ); - - return ( -
  • - - { label } - -
    -
      { content }
    - { needsTruncate && ( -
    - -
    - ) } -
    -
  • - ); -}; - -export default NavigationGroup; diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx deleted file mode 100644 index bd30dfbdad964..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import React, { useState, useRef, useCallback } from 'react'; -import NavigationGroup from './group'; -import NavigationItem from './item'; -import styles from './styles.module.scss'; -import useMenuNavigation, { NavigationContext } from './use-menu-navigation'; - -const NavigationList = ( { children } ) => ( -
      - { children } -
    -); - -const NavigationDropdown = ( { children, data } ) => { - const ref = useRef( undefined ); - const [ listOpen, setListOpen ] = useState( false ); - const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? { - label: __( 'See all results', 'jetpack-protect' ), - }; - const { label, icon } = item; - - const handleOpen = useCallback( () => { - setListOpen( open => ! open ); - }, [] ); - - return ( - - ); -}; - -const getNavigationComponent = mode => { - switch ( mode ) { - case 'list': - return NavigationList; - case 'dropdown': - return NavigationDropdown; - default: - return NavigationList; - } -}; - -const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => { - const data = useMenuNavigation( { selected, onSelect } ); - const Component = getNavigationComponent( mode ); - - return ( - - { children } - - ); -}; - -export default Navigation; -export { NavigationItem, NavigationGroup }; diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx deleted file mode 100644 index d902625c3997d..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/item.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import clsx from 'clsx'; -import React, { useContext, useEffect, useCallback } from 'react'; -import ItemBadge from './badge'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const NavigationItem = ( { - id, - label, - icon, - badge, - disabled, - onClick, - onKeyDown, - onFocus, - checked, -} ) => { - const context = useContext( NavigationContext ); - - const selected = context?.selectedItem === id; - const registerItem = context?.registerItem; - const registerRef = context?.registerRef; - const handleClickItem = context?.handleClickItem; - const handleKeyDownItem = context?.handleKeyDownItem; - const handleFocusItem = context?.handleFocusItem; - - const wrapperClassName = clsx( styles[ 'navigation-item' ], { - [ styles.clickable ]: ! disabled, - [ styles.selected ]: selected, - } ); - - const handleClick = useCallback( - evt => { - onClick?.( evt ); - handleClickItem?.( id ); - }, - [ handleClickItem, id, onClick ] - ); - - const handleKeyDown = useCallback( - evt => { - onKeyDown?.( evt ); - handleKeyDownItem?.( evt ); - }, - [ handleKeyDownItem, onKeyDown ] - ); - - const handleRef = useCallback( - ref => { - registerRef( ref, id ); - }, - [ registerRef, id ] - ); - - const handleFocus = useCallback( - evt => { - onFocus?.( evt ); - handleFocusItem?.( id ); - }, - [ handleFocusItem, id, onFocus ] - ); - - useEffect( () => { - registerItem( { id, disabled, label, icon } ); - // eslint-disable-next-line - }, [] ); - - return ( -
  • - { label } - -
  • - ); -}; - -export default NavigationItem; diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx deleted file mode 100644 index 8f075caae020a..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/label.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon } from '@wordpress/icons'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './styles.module.scss'; - -const ItemLabel = ( { icon, children, className } ) => { - return ( - - { icon && } - { children } - - ); -}; - -ItemLabel.propTypes = { - /* An icon that will be rendered before text */ - icon: PropTypes.node, - /* Label text that will be rendered */ - children: PropTypes.node.isRequired, -}; - -export default ItemLabel; diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss deleted file mode 100644 index df9e3ef1f8a25..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss +++ /dev/null @@ -1,142 +0,0 @@ -.navigation { - background-color: var( --jp-white ); - box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); - border-radius: var( --jp-border-radius ); - margin: 0; -} - -.navigation-item { - display: flex; - padding: calc( var( --spacing-base ) * 2 ); // 16px; - align-items: center; - justify-content: space-between; - margin: 0; - text-align: left; - - // Clickable State - &.clickable { - cursor: pointer; - outline-color: var( --jp-black ); - - // Focus/Hover State - &:hover:not(.selected), - &:focus:not(.selected) { - background-color: var( --jp-gray-0 ); - } - } - - // Selected State - &.selected { - background-color: var( --jp-black ); - - & .navigation-item-label { - color: var( --jp-white ) - } - - & .navigation-item-icon { - fill: var( --jp-white ); - } - - & .navigation-item-badge { - border: 1px solid var( --jp-red ); - background-color: var( --jp-red ); - color: var( --jp-white ); - } - } - - // CHILDRENS - - // .navigation-item-label - &-label { - display: flex; - align-items: center; - padding-right: var( --spacing-base ); // 8px - overflow-x: hidden; - } - - // .navigation-item-label-content - &-label-text { - display: block; - overflow-x: hidden; - text-overflow: ellipsis; - } - - // .navigation-item-icon - &-icon { - margin-right: calc( var( --spacing-base ) * 2); // 16px - } - - // .navigation-item-badge - &-badge { - border: 1px solid var( --jp-red-60 ); - color: var( --jp-red-60 ); - border-radius: 50%; - padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px - min-width: 30px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - &-check-badge { - fill: var( --jp-green-50 ); - } - - &-info-badge { - fill: var( --jp-gray-20 ); - } -} - -.navigation-group { - --icon-size: 28px; - --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px - --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px - - list-style: none; - - &-label { - padding: calc( var( --spacing-base ) * 2 ); // 16px - } - - &-content { - padding: 0; - } - - &-list { - margin-left: var( --left-spacing ) ; - } - - &-truncate { - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - justify-content: flex-start; - } -} - -.popover-text { - width: 250px; - padding: calc( var( --spacing-base ) * 2 ); // 16px -} - -.navigation-dropdown { - &-button { - display: flex; - border: 1px solid var( --jp-gray-10 ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - background-color: var( --jp-white ); - justify-content: space-between; - align-items: center; - width: 100%; - } - - &-label { - display: flex; - justify-content: flex-start; - } - - &-icon { - margin-right: var( --spacing-base ); // 8px - } -} diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js deleted file mode 100644 index 2972dac06b572..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -export const NavigationContext = React.createContext(); - -const useMenuNavigation = ( { selected, onSelect } ) => { - const [ items, setItems ] = useState( [] ); - const [ refs, setRef ] = useState( [] ); - const [ focusedItem, setFocusedItem ] = useState(); - - const handleClickItem = id => { - onSelect( id ); - }; - - const handleFocusItem = id => { - setFocusedItem( id ); - }; - - const getPrevItem = ( current, last ) => { - const startMinusOne = current - 1; - const prevIndex = startMinusOne < 0 ? last : startMinusOne; - const prevItem = items[ prevIndex ]; - return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem; - }; - - const getNextItem = ( current, last ) => { - const startPlusOne = current + 1; - const nextIndex = startPlusOne > last ? 0 : startPlusOne; - const nextItem = items[ nextIndex ]; - return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem; - }; - - const handleKeyDownItem = input => { - const code = input?.code; - const current = items.findIndex( item => item?.id === selected ); - const lastIndex = items.length - 1; - - let nextId; - - if ( code === 'ArrowUp' ) { - const prevItem = getPrevItem( current, lastIndex ); - nextId = prevItem?.id; - } else if ( code === 'ArrowDown' ) { - const nextItem = getNextItem( current, lastIndex ); - nextId = nextItem?.id; - } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) { - nextId = focusedItem; - } - - if ( nextId ) { - const element = refs[ nextId ]; - element?.focus(); - onSelect( nextId ); - } - }; - - const registerRef = ( ref, id ) => { - setRef( allRefs => { - if ( ! allRefs[ id ] && ref ) { - return { ...allRefs, [ id ]: ref }; - } - return allRefs; - } ); - }; - - const registerItem = data => { - setItems( allItems => { - const newItems = [ ...allItems ]; - const id = data?.id; - const currentIdx = newItems.findIndex( item => item?.id === id ); - - if ( currentIdx >= 0 ) { - newItems[ currentIdx ] = data; - } else { - newItems.push( data ); - } - - return newItems; - } ); - }; - - return { - selectedItem: selected, - handleClickItem, - handleKeyDownItem, - handleFocusItem, - registerRef, - registerItem, - items, - }; -}; - -export default useMenuNavigation; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index c733ff1f0a08c..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - IconTooltip, - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
    - { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
    -); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - - - { createInterpolateElement( - __( - 'The fixer is taking longer than expected. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - - - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 8304942b206d4..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,202 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -.icon-tooltip { - max-height: 20px; - margin-left: calc( var( --spacing-base ) / 2 ); // 4px - - &__icon { - color: var( --jp-red ); - } - - &__content { - color: var( --jp-gray-70 ); - font-weight: 400; - line-height: 24px; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx deleted file mode 100644 index d1100d8ce6d5e..0000000000000 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ProtectCheck(): JSX.Element { - return ( - - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx deleted file mode 100644 index e626b6af066c7..0000000000000 --- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import usePlan from '../../hooks/use-plan'; -import ButtonGroup from '../button-group'; - -/** - * Navigation for scan sections. - * - * @return {React.Element} The React Component. - */ -export default function ScanNavigation() { - const navigate = useNavigate(); - const location = useLocation(); - const { hasPlan } = usePlan(); - - const viewingScanPage = location.pathname === '/scan'; - const viewingHistoryPage = location.pathname.includes( '/scan/history' ); - const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] ); - const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] ); - - if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) { - return null; - } - - return ( - <> - - - { __( 'Scanner', 'jetpack-protect' ) } - - - { __( 'History', 'jetpack-protect' ) } - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..45a8524e60b59 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,6 +1,7 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; import styles from './styles.module.scss'; @@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } ) return ( <>
    - +
    - { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
    - ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
    - - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
    - ) } - { fixedIn && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
    - ) } - { ! description &&
    { learnMoreButton }
    } -
    - ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
    - -
    - { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
    - { getTitle() } - { hasPlan && ( -
    - { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
    - ) } -
    - { hasPlan ? ( - <> -
    - -
    - - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
    -
    - { ! scanning && ( -
    - ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
    - - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
    - ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
    { filename }
    - - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
    - ) } - { ! description &&
    { learnMoreButton }
    } - { [ 'ignored', 'current' ].includes( status ) && ( -
    - { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
    - ) } -
    - ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
    - { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
    - ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index 2338d306e6780..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx index 1468fb40ba8cf..0dfd22468079e 100644 --- a/projects/plugins/protect/src/js/routes/firewall/index.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx @@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; -import ScanFooter from '../scan/scan-footer'; import FirewallAdminSectionHero from './firewall-admin-section-hero'; import FirewallFooter from './firewall-footer'; import styles from './styles.module.scss'; @@ -576,7 +575,7 @@ const FirewallPage = () => {
    - { wafSupported ? : } + { wafSupported && } ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 141c51cde284a..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previous %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previous threats', 'jetpack-protect' ) } - - - - { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } - - - - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
    -
    - { getTitle() } -
    - -
    -
    - -
    - ) : ( - <> -
    -
    - -
    -
    -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
    - - ) } - -
    - -
    -
    - ) } - -
    - ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index d30f3e0ac3344..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..c56ae3c747f3e 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,16 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import OnboardingPopover from '../../components/onboarding-popover'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; -import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; +import styles from './styles.module.scss'; /** * Scan Page @@ -19,23 +21,41 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); + const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: [ 'current' ], + operator: 'isAny', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,16 +69,33 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } - + + + +
    + +
    + { !! status && ! isScanInProgress( status ) && ( + + ) } + { !! status && ! isScanInProgress( status ) && hasPlan && ( + + ) } + +
    +
    ); diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx index 0e85aa56d9289..c29af26bcb409 100644 --- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx +++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx @@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan'; const { siteSuffix } = window.jetpackProtectInitialState; -const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' ); -const scanResultsDescription = ( - - { __( - 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files', - 'jetpack-protect' - ) } - -); const UpgradeButton = props => { const { upgradePlan } = usePlan(); const { recordEvent } = useAnalyticsTracks(); @@ -27,11 +18,6 @@ const UpgradeButton = props => { }; export default [ - { - id: 'free-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, - }, { id: 'free-daily-scans', title: __( 'Daily automated scans', 'jetpack-protect' ), @@ -49,10 +35,41 @@ export default [ ), }, + { + id: 'paid-daily-and-manual-scans', + title: __( 'Daily & manual scanning', 'jetpack-protect' ), + description: ( + + { __( + 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', + 'jetpack-protect' + ) } + + ), + }, + { + id: 'free-scan-results', + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.', + 'jetpack-protect' + ) } + + ), + }, { id: 'paid-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.', + 'jetpack-protect' + ) } + + ), }, { id: 'paid-fix-all-threats', @@ -97,16 +114,4 @@ export default [ ), }, - { - id: 'paid-daily-and-manual-scans', - title: __( 'Daily & manual scanning', 'jetpack-protect' ), - description: ( - - { __( - 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', - 'jetpack-protect' - ) } - - ), - }, ]; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 9e1b9c102a037..db76bac1b15b0 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,33 +1,41 @@ import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); - const { hasPlan } = usePlan(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); - const { list } = useThreatsList(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const { setModal } = useModal(); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); + + const { globalStats } = useWafData(); + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); + + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); @@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => { // List of fixable threats that do not have a fix in progress const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; return ( threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) ); } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } const handleShowAutoFixersClick = threatList => { @@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => { ) : __( 'Most recent results', 'jetpack-protect' ) } - { ! hasPlan && ( - - ) } + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( @@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => { ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) ) - : sprintf( - /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No active %s', 'jetpack-protect' ), - hasPlan - ? __( 'threats', 'jetpack-protect' ) - : __( - 'vulnerabilities', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) - ) } + : __( "Don't worry about a thing", 'jetpack-protect' ) } <> - - { __( - 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', - 'jetpack-protect' - ) } - - { fixableList.length > 0 && ( + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } + + ) : ( <> - - { ! scanning && ( - -
    - -
    } secondary={ } diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 908e34f6e71d7..163fd23248aaa 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,12 +1,14 @@ -.subheading-text { - white-space: nowrap; +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.product-section, .info-section { - margin-top: calc( var( --spacing-base ) * 7 ); // 56px - margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px -} +.scan-results-container { + padding-left: 0; + padding-right: 0; + overflow: hidden; -.auto-fixers { - margin-top: calc( var( --spacing-base ) * 4 ); // 32px -} \ No newline at end of file + > * { + margin-left: calc( var( --spacing-base ) * -3 ); // -24px + margin-right: calc( var( --spacing-base ) * -3 ); // -24px + } +} diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..0c65dfec146a7 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -33,6 +33,24 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + /** + * Transpile @wordpress/dataviews in node_modules too. + * + * @see https://github.com/Automattic/jetpack/issues/39907 + */ + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress/dataviews/' ], + babelOpts: { + configFile: false, + plugins: [ + [ + require.resolve( '@automattic/babel-plugin-replace-textdomain' ), + { textdomain: 'jetpack-protect' }, + ], + ], + }, + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From f51c991434038e288eb636c31929d616f38fb029 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:22:55 -0800 Subject: [PATCH 119/290] Components: Add ScanReport (#40419) --- .../changelog/components-add-scan-report | 4 + .../components/scan-report/constants.ts | 30 +++ .../components/scan-report/index.tsx | 197 ++++++++++++++++++ .../scan-report/stories/index.stories.tsx | 72 +++++++ .../components/scan-report/styles.module.scss | 21 ++ .../components/shield-icon/index.tsx | 14 +- .../shield-icon/stories/index.stories.tsx | 6 +- projects/js-packages/components/index.ts | 1 + .../scan/changelog/components-add-scan-report | 4 + .../js-packages/scan/src/types/threats.ts | 24 ++- 10 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 projects/js-packages/components/changelog/components-add-scan-report create mode 100644 projects/js-packages/components/components/scan-report/constants.ts create mode 100644 projects/js-packages/components/components/scan-report/index.tsx create mode 100644 projects/js-packages/components/components/scan-report/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/scan-report/styles.module.scss create mode 100644 projects/js-packages/scan/changelog/components-add-scan-report diff --git a/projects/js-packages/components/changelog/components-add-scan-report b/projects/js-packages/components/changelog/components-add-scan-report new file mode 100644 index 0000000000000..ba0fbd4cce025 --- /dev/null +++ b/projects/js-packages/components/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport component diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts new file mode 100644 index 0000000000000..436eed91c5701 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -0,0 +1,30 @@ +import { __ } from '@wordpress/i18n'; +import { + code as fileIcon, + color as themeIcon, + plugins as pluginIcon, + shield as shieldIcon, + wordpress as coreIcon, +} from '@wordpress/icons'; + +export const TYPES = [ + { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, + { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, + { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, + { value: 'files', label: __( 'Files', 'jetpack-components' ) }, +]; + +export const ICONS = { + plugins: pluginIcon, + themes: themeIcon, + core: coreIcon, + files: fileIcon, + default: shieldIcon, +}; + +export const FIELD_ICON = 'icon'; +export const FIELD_TYPE = 'type'; +export const FIELD_NAME = 'name'; +export const FIELD_STATUS = 'status'; +export const FIELD_UPDATE = 'update'; +export const FIELD_VERSION = 'version'; diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx new file mode 100644 index 0000000000000..14795376f7d95 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -0,0 +1,197 @@ +import { type ScanReportExtension } from '@automattic/jetpack-scan'; +import { Tooltip } from '@wordpress/components'; +import { + type SupportedLayouts, + type View, + type Field, + DataViews, + filterSortAndPaginate, +} from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; +import { Icon } from '@wordpress/icons'; +import { useCallback, useMemo, useState } from 'react'; +import ShieldIcon from '../shield-icon'; +import { + FIELD_NAME, + FIELD_VERSION, + FIELD_ICON, + FIELD_STATUS, + FIELD_TYPE, + TYPES, + ICONS, +} from './constants'; +import styles from './styles.module.scss'; + +/** + * DataViews component for displaying a scan report. + * + * @param {object} props - Component props. + * @param {Array} props.data - Scan report data. + * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * + * @return {JSX.Element} The ScanReport component. + */ +export default function ScanReport( { data, onChangeSelection } ): JSX.Element { + const baseView = { + search: '', + filters: [], + page: 1, + perPage: 20, + }; + + /** + * DataView default layouts. + * + * This property provides layout information about the view types that are active. If empty, enables all layout types (see “Layout Types”) with empty layout data. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view + */ + const defaultLayouts: SupportedLayouts = { + table: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_TYPE, FIELD_NAME, FIELD_VERSION ], + layout: { + primaryField: FIELD_STATUS, + }, + }, + list: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_VERSION ], + layout: { + primaryField: FIELD_NAME, + mediaField: FIELD_ICON, + }, + }, + }; + + /** + * DataView view object - configures how the dataset is visible to the user. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object + */ + const [ view, setView ] = useState< View >( { + type: 'table', + ...defaultLayouts.table, + } ); + + /** + * DataView fields - describes the visible items for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object + */ + const fields = useMemo( () => { + const iconHeight = 20; + const result: Field< ScanReportExtension >[] = [ + { + id: FIELD_STATUS, + label: __( 'Status', 'jetpack-components' ), + render( { item }: { item: ScanReportExtension } ) { + let variant: 'info' | 'warning' | 'success' = 'info'; + let text = __( + 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', + 'jetpack-components' + ); + + if ( item.checked ) { + if ( item.threats.length > 0 ) { + variant = 'warning'; + text = __( 'Threat detected.', 'jetpack-components' ); + } else { + variant = 'success'; + text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + } + } + + return ( + +
    + +
    +
    + ); + }, + }, + { + id: FIELD_TYPE, + label: __( 'Type', 'jetpack-components' ), + elements: TYPES, + }, + { + id: FIELD_NAME, + label: __( 'Name', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.name ? item.name : ''; + }, + }, + { + id: FIELD_VERSION, + label: __( 'Version', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.version ? item.version : ''; + }, + }, + ...( view.type === 'list' + ? [ + { + id: FIELD_ICON, + label: __( 'Icon', 'jetpack-components' ), + enableSorting: false, + enableHiding: false, + getValue( { item }: { item: ScanReportExtension } ) { + return ICONS[ item.type ] || ''; + }, + render( { item }: { item: ScanReportExtension } ) { + return ( +
    + +
    + ); + }, + }, + ] + : [] ), + ]; + + return result; + }, [ view ] ); + + /** + * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. + * + * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts + */ + const { data: processedData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, fields ); + }, [ data, view, fields ] ); + + /** + * Callback function to update the view state. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function + */ + const onChangeView = useCallback( ( newView: View ) => { + setView( newView ); + }, [] ); + + /** + * DataView getItemId function - returns the unique ID for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function + */ + const getItemId = useCallback( ( item: ScanReportExtension ) => item.id.toString(), [] ); + + return ( + + ); +} diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx new file mode 100644 index 0000000000000..63926908850de --- /dev/null +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -0,0 +1,72 @@ +import ScanReport from '..'; + +export default { + title: 'JS Packages/Components/Scan Report', + component: ScanReport, + parameters: { + backgrounds: { + default: 'light', + values: [ { name: 'light', value: 'white' } ], + }, + }, + decorators: [ + Story => ( +
    + +
    + ), + ], +}; + +export const Default = args => ; +Default.args = { + data: [ + { + id: 1, + name: 'WordPress', + slug: null, + version: '6.7.1', + threats: [], + checked: true, + type: 'core', + }, + { + id: 2, + name: 'Jetpack', + slug: 'jetpack/jetpack.php', + version: '14.1-a.7', + threats: [], + checked: false, + type: 'plugins', + }, + { + id: 3, + name: 'Twenty Fifteen', + slug: 'twentyfifteen', + version: '1.1', + threats: [ + { + id: 198352527, + signature: 'Vulnerable.WP.Extension', + description: 'Vulnerable WordPress extension', + severity: 3, + }, + ], + checked: true, + type: 'themes', + }, + { + id: 4, + threats: [ + { + id: 198352406, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, + ], + checked: true, + type: 'files', + }, + ], +}; diff --git a/projects/js-packages/components/components/scan-report/styles.module.scss b/projects/js-packages/components/components/scan-report/styles.module.scss new file mode 100644 index 0000000000000..d313d4cb8898a --- /dev/null +++ b/projects/js-packages/components/components/scan-report/styles.module.scss @@ -0,0 +1,21 @@ +@import '@wordpress/dataviews/build-style/style.css'; + +.threat__media { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #EDFFEE; + border-color: #EDFFEE; + + svg { + fill: var( --jp-black ); + } +} + +.tooltip { + max-width: 240px; + border-radius: 4px; + text-align: left; +} \ No newline at end of file diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx index fee9f4d70c463..b07b943b5e7fa 100644 --- a/projects/js-packages/components/components/shield-icon/index.tsx +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; const COLORS = { - error: '#D63638', - warning: '#F0B849', - success: '#069E08', default: '#1d2327', + info: '#A7AAAD', + success: '#069E08', + warning: '#F0B849', + error: '#D63638', }; /** @@ -32,11 +33,11 @@ export default function ShieldIcon( { }: { className?: string; contrast?: string; - fill?: 'default' | 'success' | 'warning' | 'error' | string; + fill?: 'default' | 'info' | 'success' | 'warning' | 'error' | string; height?: number; icon?: 'success' | 'error'; outline?: boolean; - variant: 'default' | 'success' | 'warning' | 'error'; + variant: 'default' | 'info' | 'success' | 'warning' | 'error'; } ): JSX.Element { const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; const iconFill = outline ? shieldFill : contrast; @@ -60,6 +61,9 @@ export default function ShieldIcon( { } fill={ shieldFill } /> + { 'info' === iconVariant && ( + + ) } { 'success' === iconVariant && ( {
    +
    + diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index 4b0f3612012e7..6df50ee7fdb61 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -48,6 +48,7 @@ export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; export { default as ShieldIcon } from './components/shield-icon'; +export { default as ScanReport } from './components/scan-report'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/js-packages/scan/changelog/components-add-scan-report b/projects/js-packages/scan/changelog/components-add-scan-report new file mode 100644 index 0000000000000..eeb9c55de4a28 --- /dev/null +++ b/projects/js-packages/scan/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates/adds scan types diff --git a/projects/js-packages/scan/src/types/threats.ts b/projects/js-packages/scan/src/types/threats.ts index 72428c209ee49..22f6b06477163 100644 --- a/projects/js-packages/scan/src/types/threats.ts +++ b/projects/js-packages/scan/src/types/threats.ts @@ -4,6 +4,23 @@ export type ThreatStatus = 'fixed' | 'ignored' | 'current'; export type ThreatFixType = 'replace' | 'delete' | 'update' | string; +export type ScanReportExtension = { + id: number; + checked: boolean; + slug?: string; + name?: string; + version?: string; + threats: Threat[]; + type: 'plugins' | 'themes' | 'core' | 'files'; +}; + +export type Extension = { + slug: string; + name: string; + version: string; + type: 'plugins' | 'themes' | 'core'; +}; + export type Threat = { /** The threat's unique ID. */ id: string | number; @@ -57,10 +74,5 @@ export type Threat = { diff?: string; /** The affected extension. */ - extension?: { - slug: string; - name: string; - version: string; - type: 'plugins' | 'themes' | 'core'; - }; + extension?: Extension; }; From c39dadab3dbcd077ee73c6e82f2272253ac2430e Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 6 Dec 2024 12:05:11 -0700 Subject: [PATCH 120/290] Fix type errors Protect: add HMR support Revert "Protect: add HMR support" This reverts commit 06497a05bb050c86e097b36038c8742af427388d. --- projects/js-packages/scan/src/utils/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 9e2e75bcd4d91..30a96cbd132d5 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,9 +21,9 @@ export const getThreatIcon = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return 'wordpress-alt'; - case 'plugin': + case 'plugins': return 'plugins'; - case 'theme': + case 'themes': return 'appearance'; case 'file': return 'media-code'; @@ -36,9 +36,9 @@ export const getThreatSubtitle = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); - case 'plugin': + case 'plugins': return __( 'Vulnerable Plugin', 'jetpack-scan' ); - case 'theme': + case 'themes': return __( 'Vulnerable Theme', 'jetpack-scan' ); case 'file': return __( 'File Threat', 'jetpack-scan' ); From a5aad57bed2e9f3d4cae4615a8768ece09f81999 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 9 Dec 2024 14:38:56 -0700 Subject: [PATCH 121/290] Protect: Refactor AdminSectionHero (#40516) --- .../components/admin-section-hero/index.tsx | 96 +++++----- .../stories/index.stories.jsx | 16 +- .../admin-section-hero/styles.module.scss | 44 +++-- .../error-admin-section-hero/index.tsx | 30 ++- .../styles.module.scss | 6 +- .../firewall/firewall-admin-section-hero.tsx | 27 ++- .../js/routes/firewall/firewall-statcards.jsx | 2 +- .../src/js/routes/firewall/styles.module.scss | 31 ++- .../routes/scan/scan-admin-section-hero.tsx | 177 +++++++++--------- .../scan/scanning-admin-section-hero.tsx | 70 ++++--- .../src/js/routes/scan/styles.module.scss | 10 + 11 files changed, 278 insertions(+), 231 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ccf607698084..7638936db5139 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -2,67 +2,73 @@ import { AdminSectionHero as JetpackAdminSectionHero, H3, ShieldIcon, + Container, + Col, } from '@automattic/jetpack-components'; -import SeventyFiveLayout from '../seventy-five-layout'; +import clsx from 'clsx'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; -interface AdminSectionHeroProps { - main: React.ReactNode; - secondary?: React.ReactNode; - preserveSecondaryOnMobile?: boolean; - spacing?: number; -} - -interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { - children: React.ReactNode; - showIcon?: boolean; - variant?: 'default' | 'success' | 'error'; - outline?: boolean; - } >; - Subheading: React.FC< { children: React.ReactNode } >; -} - -const AdminSectionHero: AdminSectionHeroComponent = ( { - main, - secondary, - preserveSecondaryOnMobile = true, - spacing = 7, -} ) => { +const AdminSectionHero = ( { + children, + ...props +}: React.ComponentProps< typeof JetpackAdminSectionHero > ) => { return ( - + - + + +
    { children }
    + +
    ); }; -AdminSectionHero.Heading = ( { +AdminSectionHero.Main = ( { children, - variant = 'default', - showIcon = false, + className, + ...props }: { children: React.ReactNode; - variant?: 'default' | 'success' | 'error'; - showIcon?: boolean; + className?: string; + [ key: string ]: unknown; +} ) => { + return ( +
    + { children } +
    + ); +}; + +AdminSectionHero.Aside = ( { + children, + className, + ...props +}: React.ComponentProps< 'div' > & { + className?: string; } ) => { return ( -

    +
    { children } - { showIcon && ( +
    + ); +}; + +AdminSectionHero.Heading = ( { + children, + icon, + ...props +}: React.ComponentProps< typeof H3 > & { + icon?: 'default' | 'success' | 'error'; +} ) => { + return ( +

    + { children } + { !! icon && ( { - return
    { children }
    ; -}; - export default AdminSectionHero; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index ca2dfda7fc98e..59ed9086d6317 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -9,16 +9,16 @@ export default { export const Default = args => ; Default.args = { - main: ( + children: ( <> - - - { 'No threats found' } - - + + + { 'No threats found' } { 'Most recent results' } - + + + + ), - secondary: , }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index a414aa9216f5c..74cfe29aaaded 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -1,15 +1,39 @@ -.header-main { +.admin-section-hero { display: flex; flex-direction: column; - justify-content: center; - align-items: flex-start; + gap: calc( var( --spacing-base ) * 6 ); // 48px + + max-width: var(--max-container-width); + padding: calc( var( --spacing-base ) * 6 ) 0; // 48px 0 + margin: 0 auto; + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media ( min-width: 1100px ) { + flex-direction: row; + align-items: center; + gap: calc( var( --spacing-base ) * 3 ); // 24px + } } -.header-secondary { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; +.admin-section-hero__main { + flex: 2; +} + +.admin-section-hero__aside { + flex: 1; + flex-shrink: 0; + + @media ( min-width: 1200px ) { + display: flex; + justify-content: flex-end; + } } .heading-icon { @@ -17,10 +41,6 @@ margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } -.subheading { - width: fit-content; -} - .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 1a9bc87387fa9..536d8f50de7d1 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -1,6 +1,5 @@ -import { Text } from '@automattic/jetpack-components'; +import { ShieldIcon, Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; import styles from './styles.module.scss'; @@ -19,22 +18,17 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { displayErrorMessage += ' ' + __( 'Try again in a few minutes.', 'jetpack-protect' ); return ( - - -
    - - { __( 'An error occurred', 'jetpack-protect' ) } -
    -
    - - { displayErrorMessage } - - - } - preserveSecondaryOnMobile={ false } - /> + + + +
    + { __( 'An error occurred', 'jetpack-protect' ) } + +
    +
    + { displayErrorMessage } +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss index 6f0750abd02f8..1c89377d4b4b5 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss @@ -4,11 +4,7 @@ } .warning { - width: 54px; - height: 54px; - fill: var( --jp-red ); - margin-left: -8px; - margin-right: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px } .scan-navigation { diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 3f70a75509b76..837f649c67f16 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -5,6 +5,7 @@ import AdminSectionHero from '../../components/admin-section-hero'; import useWafData from '../../hooks/use-waf-data'; import FirewallStatCards from './firewall-statcards'; import FirewallSubheading from './firewall-subheading'; +import styles from './styles.module.scss'; const FirewallAdminSectionHero = () => { const { @@ -84,16 +85,22 @@ const FirewallAdminSectionHero = () => { }, [ status ] ); return ( - - - { heading } - { subheading } - - } - secondary={ wafSupported && } - /> + + + + { heading } + { subheading } + + { wafSupported && ( + + + + ) } + ); }; diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 15c80df763c18..1eebd67cb60d7 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -93,7 +93,7 @@ const FirewallStatCards = () => { ); return ( -
    +
    diff --git a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss index afcbc2ad69b30..9404db7b56f09 100644 --- a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss @@ -3,6 +3,10 @@ max-width: calc( 744px + ( var( --spacing-base ) * 6 ) ); // 744px + 48px (desired inner width + horizontal padding) } +.status { + margin-bottom: calc( var( --spacing-base ) * 2 ); // 16px +} + .toggle-section { display: flex; @@ -145,14 +149,10 @@ align-items: center; } -.stat-card-wrapper { +.stat-cards-wrapper { display: flex; - margin-left: auto; - flex-wrap: wrap; - - >:first-child { - margin-right: calc( var( --spacing-base ) * 3 ); // 24px - } + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 3 ); // 24px .disabled { opacity: 0.5; @@ -220,6 +220,23 @@ background-color: var( --jp-white-off ); } +@media ( max-width: 1200px ) { + .stat-cards-wrapper { + justify-content: flex-start; + } +} + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} + .standalone-mode, .share-data { display: flex; flex-direction: column; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index db76bac1b15b0..4257c585351eb 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -59,6 +59,28 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } + let heading = __( "Don't worry about a thing", 'jetpack-protect' ); + if ( numThreats > 0 ) { + if ( hasPlan ) { + heading = sprintf( + /* translators: %s: Total number of threats */ + _n( '%1$s active threat', '%1$s active threats', numThreats, 'jetpack-protect' ), + numThreats + ); + } else { + heading = sprintf( + /* translators: %s: Total number of vulnerabilities */ + _n( + '%1$s active vulnerability', + '%1$s active vulnerabilities', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + } + const handleShowAutoFixersClick = threatList => { return event => { event.preventDefault(); @@ -84,94 +106,79 @@ const ScanAdminSectionHero: React.FC = () => { } return ( - - - { lastCheckedLocalTimestamp - ? sprintf( - // translators: %s: date and time of the last scan - __( '%s results', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) - ) - : __( 'Most recent results', 'jetpack-protect' ) } + + + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS, g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + + 0 ? 'error' : 'success' }> + { heading } + + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } - - 0 ? 'error' : 'success' }> - { numThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s active %2$s', 'jetpack-protect' ), - numThreats, - hasPlan - ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) - : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) - ) - : __( "Don't worry about a thing", 'jetpack-protect' ) } - - - <> - { hasPlan ? ( - - { __( - "We actively review your site's files line-by-line to identify threats and vulnerabilities.", - 'jetpack-protect' - ) } - - ) : ( - <> - - { sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - + ) : ( + <> + + { sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted ) } - { fixableList.length > 0 && ( - <> -
    - -
    -
    + -
    - - } - /> + > + + + + ) } + { fixableList.length > 0 && ( + <> +
    + +
    +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx index 4db4449b60119..ac9e0137cd170 100644 --- a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx @@ -18,44 +18,38 @@ const ScanningAdminSectionHero: React.FC = () => { : totalVulnerabilities.toLocaleString(); return ( - - - { __( 'Your results will be ready soon', 'jetpack-protect' ) } - - - <> - { hasPlan && ( - - ) } - - { hasPlan - ? __( - "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", - 'jetpack-protect' - ) - : sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - } - secondary={ } - preserveSecondaryOnMobile={ false } - spacing={ 4 } - /> + + + + { __( 'Your results will be ready soon', 'jetpack-protect' ) } + + { hasPlan && ( + + ) } + + { hasPlan + ? __( + "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", + 'jetpack-protect' + ) + : sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted + ) } + + + + + + ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 163fd23248aaa..5806ca5353863 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,3 +1,7 @@ +.scanning-main { + max-width: 512px; +} + .auto-fixers { margin-top: calc( var( --spacing-base ) * 4 ); // 32px } @@ -12,3 +16,9 @@ margin-right: calc( var( --spacing-base ) * -3 ); // -24px } } + +.progress-animation { + @media (max-width: 1099px) { + display: none; + } +} From 32e9291971bb3b0f557ce03da6369edf5b1dc33d Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:04:08 -0800 Subject: [PATCH 122/290] Protect: Update Scan History extension types (#40548) --- .../protect/src/class-scan-history.php | 4 +++ .../js/routes/firewall/firewall-footer.jsx | 2 -- .../src/js/routes/firewall/styles.module.scss | 30 ------------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index 8ea1dec7156e7..23019ccd634ad 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -219,6 +219,10 @@ private static function normalize_api_data( $scan_data ) { } foreach ( $scan_data->threats as $source_threat ) { + if ( ! empty( $source_threat->extension ) && in_array( $source_threat->extension->type, array( 'plugin', 'theme' ), true ) ) { + $source_threat->extension->type .= 's'; + } + $history->threats[] = new Threat_Model( $source_threat ); } diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx index 0e28d7bae7c98..0c175b1cd651f 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx @@ -69,7 +69,6 @@ const ShareData = () => {
    { __( 'Share data with Jetpack', 'jetpack-protect' ) } { ) } /> :first-child { - margin-right: 0; - margin-bottom: var( --spacing-base ); // 8px - } - } - - .stat-card-icon { - margin-bottom: 0; - } -} - -.share-data-section { - display: flex; - - .share-data-toggle { - margin-top: calc( var( --spacing-base ) / 2 ); // 4px - margin-right: var( --spacing-base ); // 8px - } -} - .icon-tooltip { max-height: 20px; margin-left: calc( var( --spacing-base ) / 2 ); // 4px From bacace263e60b8926e62c9d7380251d29020cf5f Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:25:25 -0800 Subject: [PATCH 123/290] Protect: Add Home page (#40317) * Init project branch * Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller * Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller * Protect: de-emphasize cloud link by using link variant (#40211) * Protect: add ShieldIcon component * Protect: Add ShieldIcon Component (#40402) * Protect: Integrate ThreatsDataViews Component (#40076) * Components: Add ScanReport (#40419) * Fix type errors * Protect: add home page --------- Co-authored-by: Nate Weller Co-authored-by: Nate Weller Co-authored-by: Dean Kmyta --- .../add-hide-value-prop-to-stat-card | 4 + .../components/components/stat-card/index.tsx | 13 +- .../components/components/stat-card/types.ts | 5 + .../protect/changelog/add-protect-home | 4 + .../protect/src/class-jetpack-protect.php | 3 +- .../src/js/components/admin-page/index.jsx | 1 + .../src/js/components/pricing-table/index.jsx | 2 +- .../components/seventy-five-layout/index.tsx | 73 ----- .../seventy-five-layout/styles.module.scss | 13 - .../plugins/protect/src/js/hooks/use-plan.tsx | 2 +- projects/plugins/protect/src/js/index.tsx | 4 +- .../routes/home/home-admin-section-hero.tsx | 50 ++++ .../src/js/routes/home/home-statcards.jsx | 274 ++++++++++++++++++ .../protect/src/js/routes/home/index.jsx | 25 ++ .../src/js/routes/home/styles.module.scss | 69 +++++ 15 files changed, 449 insertions(+), 93 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card create mode 100644 projects/plugins/protect/changelog/add-protect-home delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss create mode 100644 projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx create mode 100644 projects/plugins/protect/src/js/routes/home/home-statcards.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/styles.module.scss diff --git a/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card new file mode 100644 index 0000000000000..0d4002c768dd8 --- /dev/null +++ b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Stat Card: add hideValue prop diff --git a/projects/js-packages/components/components/stat-card/index.tsx b/projects/js-packages/components/components/stat-card/index.tsx index b6854dc02f37e..222cafb44068e 100644 --- a/projects/js-packages/components/components/stat-card/index.tsx +++ b/projects/js-packages/components/components/stat-card/index.tsx @@ -18,7 +18,14 @@ import type React from 'react'; * @param {StatCardProps} props - Component props. * @return {React.ReactNode} - StatCard react component. */ -const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCardProps ) => { +const StatCard = ( { + className, + icon, + label, + value, + variant = 'square', + hideValue = false, +}: StatCardProps ) => { const formattedValue = numberFormat( value ); const compactValue = numberFormat( value, { notation: 'compact', @@ -33,12 +40,12 @@ const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCa { variant === 'square' ? ( - { compactValue } + { hideValue ? '-' : compactValue } ) : ( - { formattedValue } + { hideValue ? '-' : formattedValue } ) }
    diff --git a/projects/js-packages/components/components/stat-card/types.ts b/projects/js-packages/components/components/stat-card/types.ts index 4b0fd698e6774..8e1c0e99d6d60 100644 --- a/projects/js-packages/components/components/stat-card/types.ts +++ b/projects/js-packages/components/components/stat-card/types.ts @@ -25,4 +25,9 @@ export type StatCardProps = { * @default 'square' */ variant?: 'square' | 'horizontal'; + + /** + * Whether to hide the value. + */ + hideValue?: boolean; }; diff --git a/projects/plugins/protect/changelog/add-protect-home b/projects/plugins/protect/changelog/add-protect-home new file mode 100644 index 0000000000000..0bcfedb6fe8ac --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds a Home page and StatCards diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 293ccdaeb3ce7..492cded402990 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -457,8 +457,9 @@ public static function get_waf_stats() { } return array( - 'blockedRequests' => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false, + 'blockedRequests' => Waf_Stats::get_blocked_requests(), 'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(), + 'blockedLogins' => (int) get_option( 'jetpack_protect_blocked_attempts', 0 ), ); } } diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 68f9359a9bd81..5811238cd266e 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -63,6 +63,7 @@ const AdminPage = ( { children } ) => { { notice && } + { const getProtectFree = useCallback( async () => { recordEvent( 'jetpack_protect_connected_product_activated' ); await connectSiteMutation.mutateAsync(); - navigate( '/scan' ); + navigate( '/' ); }, [ connectSiteMutation, recordEvent, navigate ] ); const args = { diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx b/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx deleted file mode 100644 index 19ee4309e55a5..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Container, Col, useBreakpointMatch } from '@automattic/jetpack-components'; -import React from 'react'; - -// Define the props interface for the SeventyFiveLayout component -interface SeventyFiveLayoutProps { - spacing?: number; - gap?: number; - main: React.ReactNode; - mainClassName?: string; - secondary: React.ReactNode; - secondaryClassName?: string; - preserveSecondaryOnMobile?: boolean; - fluid?: boolean; -} - -/** - * SeventyFive layout meta component - * The component name references to - * the sections disposition of the layout. - * FiftyFifty, 75, thus 7|5 means the cols numbers - * for main and secondary sections respectively, - * in large lg viewport size. - * - * @param {object} props - Component props - * @param {number} props.spacing - Horizontal spacing - * @param {number} props.gap - Horizontal gap - * @param {React.ReactNode} props.main - Main section component - * @param {string} props.mainClassName - Main section class name - * @param {React.ReactNode} props.secondary - Secondary section component - * @param {string} props.secondaryClassName - Secondary section class name - * @param {boolean} props.preserveSecondaryOnMobile - Whether to show secondary section on mobile - * @param {boolean} props.fluid - Whether to use fluid layout - * @return {React.ReactNode} - React meta-component - */ -const SeventyFiveLayout: React.FC< SeventyFiveLayoutProps > = ( { - spacing = 0, - gap = 0, - main, - mainClassName, - secondary, - secondaryClassName, - preserveSecondaryOnMobile = false, - fluid, -} ) => { - // Ensure the correct typing for useBreakpointMatch - const [ isSmall, isLarge ] = useBreakpointMatch( [ 'sm', 'lg' ] ); - - /* - * By convention, secondary section is not shown when: - * - preserveSecondaryOnMobile is false - * - on mobile breakpoint (sm) - */ - const hideSecondarySection = ! preserveSecondaryOnMobile && isSmall; - - return ( - - { ! hideSecondarySection && ( - <> - - { main } - - { isLarge && } - - { secondary } - - - ) } - { hideSecondarySection && { main } } - - ); -}; - -export default SeventyFiveLayout; diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss b/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss deleted file mode 100644 index 5405c6e28a9b4..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -// seventy-five layout -// Handle large lg size from here, -// adding a gap on one column -// in between main and secondary sections. -@media ( min-width: 960px ) { - .main { - grid-column: 1 / span 6; - } - - .secondary { - grid-column: 8 / span 5; - } -} diff --git a/projects/plugins/protect/src/js/hooks/use-plan.tsx b/projects/plugins/protect/src/js/hooks/use-plan.tsx index b5ab18da01875..f5cd1d54943b9 100644 --- a/projects/plugins/protect/src/js/hooks/use-plan.tsx +++ b/projects/plugins/protect/src/js/hooks/use-plan.tsx @@ -48,7 +48,7 @@ export default function usePlan( { redirectUrl }: { redirectUrl?: string } = {} const { run: checkout } = useProductCheckoutWorkflow( { productSlug: JETPACK_SCAN_SLUG, - redirectUrl: redirectUrl || adminUrl, + redirectUrl: redirectUrl || adminUrl + '#/scan', siteProductAvailabilityHandler: API.checkPlan, useBlogIdSuffix: true, connectAfterCheckout: false, diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index 2b91f4b090b92..4438d5021a664 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -11,6 +11,7 @@ import { NoticeProvider } from './hooks/use-notices'; import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; +import HomeRoute from './routes/home'; import ScanRoute from './routes/scan'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -56,6 +57,7 @@ function render() { } /> + } /> } /> } /> - } /> + } /> diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx new file mode 100644 index 0000000000000..12d887e933f43 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -0,0 +1,50 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import AdminSectionHero from '../../components/admin-section-hero'; +import usePlan from '../../hooks/use-plan'; +import HomeStatCards from './home-statcards'; +import styles from './styles.module.scss'; + +const HomeAdminSectionHero: React.FC = () => { + const { hasPlan } = usePlan(); + const navigate = useNavigate(); + const handleScanReportClick = useCallback( () => { + navigate( '/scan' ); + }, [ navigate ] ); + + return ( + + + <> + + { __( 'Your site is safe with us', 'jetpack-protect' ) } + + + { hasPlan + ? __( + 'We stay ahead of security threats to keep your site protected.', + 'jetpack-protect' + ) + : __( + 'We stay ahead of security vulnerabilities to keep your site protected.', + 'jetpack-protect' + ) } + + + + + { } + + ); +}; + +export default HomeAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx new file mode 100644 index 0000000000000..2d1dc34cac147 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx @@ -0,0 +1,274 @@ +import { Text, useBreakpointMatch, StatCard, ShieldIcon } from '@automattic/jetpack-components'; +import { Spinner, Tooltip } from '@wordpress/components'; +import { dateI18n } from '@wordpress/date'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import usePlan from '../../hooks/use-plan'; +import useWafData from '../../hooks/use-waf-data'; +import styles from './styles.module.scss'; + +const IconWithLabel = ( { label, isSmall, icon } ) => ( + + { icon } + { ! isSmall && ( + + { label } + + ) } + +); + +const HomeStatCard = ( { text, args } ) => ( + +
    + +
    +
    +); + +const HomeStatCards = () => { + const ICON_HEIGHT = 20; + + const { hasPlan } = usePlan(); + const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); + + const { data: status } = useScanStatusQuery(); + const scanning = isScanInProgress( status ); + const numThreats = status.threats.length; + const scanError = status.error; + + let lastCheckedLocalTimestamp = null; + if ( status.lastChecked ) { + // Convert the lastChecked UTC date to a local timestamp + lastCheckedLocalTimestamp = dateI18n( + 'F jS g:i A', + new Date( status.lastChecked + ' UTC' ).getTime(), + false + ); + } + + const { + config: { bruteForceProtection: isBruteForceModuleEnabled }, + isEnabled: isWafModuleEnabled, + wafSupported, + stats, + } = useWafData(); + + const { + blockedRequests: { allTime: allTimeBlockedRequestsCount = 0 } = {}, + blockedLogins: allTimeBlockedLoginsCount = 0, + } = stats || {}; + + const variant = useMemo( () => ( isSmall ? 'horizontal' : 'square' ), [ isSmall ] ); + + const lastCheckedMessage = useMemo( () => { + if ( scanning ) { + return __( 'Your results will be ready soon.', 'jetpack-protect' ); + } + + if ( scanError ) { + return __( + 'Please check your connection or try scanning again in a few minutes.', + 'jetpack-protect' + ); + } + + if ( lastCheckedLocalTimestamp ) { + if ( numThreats > 0 ) { + if ( hasPlan ) { + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d threat.', + 'Last checked on %1$s: We found %2$d threats.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d vulnerability.', + 'Last checked on %1$s: We found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %s: date/time + __( 'Last checked on %s: Your site is secure.', 'jetpack-protect' ), + lastCheckedLocalTimestamp + ); + } + if ( hasPlan ) { + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %d threat.', + 'Last scan we found %d threats.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %2$d vulnerability.', + 'Last scan we found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + }, [ scanError, scanning, numThreats, lastCheckedLocalTimestamp, hasPlan ] ); + + const scanArgs = useMemo( () => { + let scanIcon; + if ( scanning ) { + scanIcon = ; + } else if ( scanError ) { + scanIcon = ; + } else { + scanIcon = ( + + ); + } + + let scanLabel; + if ( scanning ) { + scanLabel = __( 'One moment, please…', 'jetpack-protect' ); + } else if ( scanError ) { + scanLabel = __( 'An error occurred', 'jetpack-protect' ); + } else if ( hasPlan ) { + scanLabel = _n( 'Threat identified', 'Threats identified', numThreats, 'jetpack-protect' ); + } else { + scanLabel = _n( + 'Vulnerability identified', + 'Vulnerabilities identified', + numThreats, + 'jetpack-protect' + ); + } + + return { + variant, + icon: ( + + ), + label: { scanLabel }, + value: numThreats, + hideValue: !! ( scanError || scanning ), + }; + }, [ variant, scanning, ICON_HEIGHT, scanError, numThreats, hasPlan, isSmall ] ); + + const wafArgs = useMemo( + () => ( { + variant: variant, + className: isWafModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Firewall', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked requests', 'jetpack-protect' ) } + + ), + value: allTimeBlockedRequestsCount, + hideValue: ! isWafModuleEnabled, + } ), + [ variant, isWafModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedRequestsCount ] + ); + + const bruteForceArgs = useMemo( + () => ( { + variant: variant, + className: isBruteForceModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Brute force', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked login attempts', 'jetpack-protect' ) } + + ), + value: allTimeBlockedLoginsCount, + hideValue: ! isBruteForceModuleEnabled, + } ), + [ variant, isBruteForceModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedLoginsCount ] + ); + + return ( +
    + + { wafSupported && ( + + ) } + +
    + ); +}; + +export default HomeStatCards; diff --git a/projects/plugins/protect/src/js/routes/home/index.jsx b/projects/plugins/protect/src/js/routes/home/index.jsx new file mode 100644 index 0000000000000..718349caaac3f --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/index.jsx @@ -0,0 +1,25 @@ +import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import AdminPage from '../../components/admin-page'; +import HomeAdminSectionHero from './home-admin-section-hero'; + +/** + * Home Page + * + * The entry point for the Home page. + * + * @return {Component} The root component for the scan page. + */ +const HomePage = () => { + return ( + + + + + { /* TODO: Add ScanReport component here */ } + + + + ); +}; + +export default HomePage; diff --git a/projects/plugins/protect/src/js/routes/home/styles.module.scss b/projects/plugins/protect/src/js/routes/home/styles.module.scss new file mode 100644 index 0000000000000..b99bead52dbdb --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/styles.module.scss @@ -0,0 +1,69 @@ +.product-section, .info-section { + margin-top: calc( var( --spacing-base ) * 7 ); // 56px + margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px +} + +.view-scan-report { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px +} + +.stat-cards-wrapper { + display: flex; + justify-content: flex-start; + + > *:not( last-child ) { + margin-right: calc( var( --spacing-base ) * 3 ); // 24px + } + + .disabled { + opacity: 0.5; + } +} + +.stat-card-icon { + width: 100%; + margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px + display: flex; + align-items: center; + gap: 8px; + + svg { + margin: 0; + } + + .active { + fill: var( --jp-green-40 ); + } + + .warning { + fill: var( --jp-yellow-40 ); + } + + .disabled { + fill: var( --jp-gray-40 ); + } + + &-label { + color: var( --jp-black ); + white-space: nowrap; + } +} + +.stat-card-tooltip { + margin-top: 8px; + max-width: 240px; + border-radius: 4px; + text-align: left; +} + + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} \ No newline at end of file From 2a53e76500da56a27c533932d42563d9e8af1e60 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:17:14 -0800 Subject: [PATCH 124/290] Protect: Integrate ScanReport (#40420) --- .../components/scan-report/constants.ts | 6 +++ .../components/scan-report/index.tsx | 48 +++++++++++++++++-- .../scan-report/stories/index.stories.tsx | 7 +++ .../threats-data-views/constants.ts | 1 + .../components/threats-data-views/index.tsx | 13 +---- .../update-protect-add-scan-report-to-home | 4 ++ .../firewall/firewall-admin-section-hero.tsx | 4 +- .../js/routes/firewall/firewall-statcards.jsx | 22 ++++----- .../routes/home/home-admin-section-hero.tsx | 3 +- .../protect/src/js/routes/home/index.jsx | 26 ++++++++-- .../src/js/routes/home/styles.module.scss | 13 +++-- 11 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-add-scan-report-to-home diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts index 436eed91c5701..6a10d008b876f 100644 --- a/projects/js-packages/components/components/scan-report/constants.ts +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -7,6 +7,12 @@ import { wordpress as coreIcon, } from '@wordpress/icons'; +export const STATUS_TYPES = [ + { value: 'checked', label: __( 'Checked', 'jetpack-components' ) }, + { value: 'unchecked', label: __( 'Unchecked', 'jetpack-components' ) }, + { value: 'threat', label: __( 'Threat', 'jetpack-components' ) }, +]; + export const TYPES = [ { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx index 14795376f7d95..4600ecf98d9db 100644 --- a/projects/js-packages/components/components/scan-report/index.tsx +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -7,7 +7,7 @@ import { DataViews, filterSortAndPaginate, } from '@wordpress/dataviews'; -import { __ } from '@wordpress/i18n'; +import { __, _n } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; import ShieldIcon from '../shield-icon'; @@ -17,6 +17,7 @@ import { FIELD_ICON, FIELD_STATUS, FIELD_TYPE, + STATUS_TYPES, TYPES, ICONS, } from './constants'; @@ -26,12 +27,13 @@ import styles from './styles.module.scss'; * DataViews component for displaying a scan report. * * @param {object} props - Component props. + * @param {string} props.dataSource - Data source. * @param {Array} props.data - Scan report data. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. * * @return {JSX.Element} The ScanReport component. */ -export default function ScanReport( { data, onChangeSelection } ): JSX.Element { +export default function ScanReport( { dataSource, data, onChangeSelection } ): JSX.Element { const baseView = { search: '', filters: [], @@ -84,8 +86,19 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { const result: Field< ScanReportExtension >[] = [ { id: FIELD_STATUS, + elements: STATUS_TYPES, label: __( 'Status', 'jetpack-components' ), + getValue( { item } ) { + if ( item.checked ) { + if ( item.threats.length > 0 ) { + return 'threat'; + } + return 'checked'; + } + return 'unchecked'; + }, render( { item }: { item: ScanReportExtension } ) { + const scanApi = 'scan_api' === dataSource; let variant: 'info' | 'warning' | 'success' = 'info'; let text = __( 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', @@ -95,10 +108,34 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { if ( item.checked ) { if ( item.threats.length > 0 ) { variant = 'warning'; - text = __( 'Threat detected.', 'jetpack-components' ); + text = _n( + 'Vulnerability detected.', + 'Vulnerabilities detected.', + item.threats.length, + 'jetpack-components' + ); + + if ( scanApi ) { + text = _n( + 'Threat detected.', + 'Threats detected.', + item.threats.length, + 'jetpack-components' + ); + } } else { variant = 'success'; - text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + text = __( + 'No known vulnerabilities found that affect this version.', + 'jetpack-components' + ); + + if ( scanApi ) { + text = __( + 'No known threats found that affect this version.', + 'jetpack-components' + ); + } } } @@ -127,6 +164,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { { id: FIELD_VERSION, label: __( 'Version', 'jetpack-components' ), + enableSorting: false, enableGlobalSearch: true, getValue( { item }: { item: ScanReportExtension } ) { return item.version ? item.version : ''; @@ -155,7 +193,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { ]; return result; - }, [ view ] ); + }, [ view, dataSource ] ); /** * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx index 63926908850de..eebcbc428fb39 100644 --- a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -20,6 +20,7 @@ export default { export const Default = args => ; Default.args = { + dataSource: 'scan_api', data: [ { id: 1, @@ -64,6 +65,12 @@ Default.args = { title: 'Malicious code found in file: jptt_eicar.php', severity: 1, }, + { + id: 198352407, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, ], checked: true, type: 'files', diff --git a/projects/js-packages/components/components/threats-data-views/constants.ts b/projects/js-packages/components/components/threats-data-views/constants.ts index cc37c28c7b39b..40941b43ad032 100644 --- a/projects/js-packages/components/components/threats-data-views/constants.ts +++ b/projects/js-packages/components/components/threats-data-views/constants.ts @@ -19,6 +19,7 @@ export const THREAT_TYPES = [ { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'file', label: __( 'File', 'jetpack-components' ) }, + { value: '', label: __( 'Unknown', 'jetpack-components' ) }, ]; export const THREAT_ICONS = { diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 44efc998dc3ae..27cbfa23935fe 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -265,18 +265,7 @@ export default function ThreatsDataViews( { label: __( 'Type', 'jetpack-components' ), elements: THREAT_TYPES, getValue( { item }: { item: Threat } ) { - switch ( getThreatType( item ) ) { - case 'core': - return __( 'WordPress', 'jetpack-components' ); - case 'plugins': - return __( 'Plugin', 'jetpack-components' ); - case 'themes': - return __( 'Theme', 'jetpack-components' ); - case 'file': - return __( 'File', 'jetpack-components' ); - default: - return __( 'Unknown', 'jetpack-components' ); - } + return getThreatType( item ) ?? ''; }, }, { diff --git a/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home new file mode 100644 index 0000000000000..0478ae51501b8 --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport to HomeRoute diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 837f649c67f16..c302f93dd8863 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -88,12 +88,12 @@ const FirewallAdminSectionHero = () => { { heading } - { subheading } + { subheading } { wafSupported && ( diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 1eebd67cb60d7..7b1fd7cbbbede 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -1,13 +1,11 @@ -import { Text, useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; +import { useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; import { __, sprintf } from '@wordpress/i18n'; import { Icon, shield, chartBar } from '@wordpress/icons'; import { useCallback, useMemo } from 'react'; -import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; import styles from './styles.module.scss'; const FirewallStatCards = () => { - const { hasPlan } = usePlan(); const { config: { bruteForceProtection: isBruteForceModuleEnabled }, isEnabled: isWafModuleEnabled, @@ -22,26 +20,22 @@ const FirewallStatCards = () => { const { currentDay: currentDayBlockCount, thirtyDays: thirtyDayBlockCounts } = stats ? stats.blockedRequests : { currentDay: 0, thirtyDays: 0 }; - const isFeatureDisabled = ! isSupportedWafFeatureEnabled || ! hasPlan; const defaultArgs = useMemo( () => ( { - className: isFeatureDisabled ? styles.disabled : styles.active, + className: ! isSupportedWafFeatureEnabled ? styles.disabled : styles.active, variant: isSmall ? 'horizontal' : 'square', } ), - [ isFeatureDisabled, isSmall ] + [ isSupportedWafFeatureEnabled, isSmall ] ); const StatCardIcon = useCallback( ( { icon } ) => ( - { ! isSmall && ! hasPlan && ( - { __( 'Paid feature', 'jetpack-protect' ) } - ) } ), - [ isSmall, hasPlan ] + [] ); const StatCardLabel = useCallback( @@ -77,9 +71,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : currentDayBlockCount, + value: ! isSupportedWafFeatureEnabled ? 0 : currentDayBlockCount, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, currentDayBlockCount ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, currentDayBlockCount ] ); const thirtyDaysArgs = useMemo( @@ -87,9 +81,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : thirtyDayBlockCounts, + value: ! isSupportedWafFeatureEnabled ? 0 : thirtyDayBlockCounts, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, thirtyDayBlockCounts ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, thirtyDayBlockCounts ] ); return ( diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx index 12d887e933f43..d695f05eea9cc 100644 --- a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -29,7 +29,8 @@ const HomeAdminSectionHero: React.FC = () => { ) : __( 'We stay ahead of security vulnerabilities to keep your site protected.', - 'jetpack-protect' + 'jetpack-protect', + /* dummy arg to avoid bad minification */ 0 ) }

    } size="large" { ...modalProps } >
    - - +
    ); diff --git a/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx new file mode 100644 index 0000000000000..7a7fdf08b8089 --- /dev/null +++ b/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx @@ -0,0 +1,159 @@ +import { useCallback, useState } from 'react'; +import Button from '../../button/index.js'; +import ThreatsModal from '../index.js'; + +export default { + title: 'JS Packages/Components/Threats Modal', + component: ThreatsModal, +}; + +const Base = args => { + const [ isOpen, setIsOpen ] = useState( false ); + const onClick = useCallback( () => setIsOpen( true ), [] ); + const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + + return ( +
    + + { isOpen ? ( + + ) : null } +
    + ); +}; + +export const ThreatResult = Base.bind( {} ); +ThreatResult.args = { + currentThreats: [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { + fixer: 'rollback', + target: 'January 26, 2024, 6:49 am', + extensionStatus: '', + }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const ThreatsResult = Base.bind( {} ); +ThreatsResult.args = { + isUserConnected: true, + hasConnectedOwner: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const AdditionalConnectionsNeeded = Base.bind( {} ); +AdditionalConnectionsNeeded.args = { + isUserConnected: false, + hasConnectedOwner: false, + credentials: false, + credentialsRedirectUrl: '#', + handleConnectUser: () => {}, + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const UserConnectionNeeded = Base.bind( {} ); +UserConnectionNeeded.args = { + isUserConnected: false, + hasConnectedOwner: false, + handleConnectUser: () => {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const CredentialsNeeded = Base.bind( {} ); +CredentialsNeeded.args = { + isUserConnected: true, + hasConnectedOwner: true, + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss similarity index 100% rename from projects/js-packages/components/components/threat-modal/styles.module.scss rename to projects/js-packages/components/components/threats-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threats-modal/threat-actions.tsx b/projects/js-packages/components/components/threats-modal/threat-actions.tsx new file mode 100644 index 0000000000000..58adcd217a97f --- /dev/null +++ b/projects/js-packages/components/components/threats-modal/threat-actions.tsx @@ -0,0 +1,112 @@ +import { getFixerState, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useContext, useMemo } from 'react'; +import { Button } from '@automattic/jetpack-components'; +import FixerStateNotice from './fixer-state-notice'; +import styles from './styles.module.scss'; +import { ThreatsModalContext } from '.'; + +/** + * ThreatActions component + * + * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. + */ +const ThreatActions = (): JSX.Element => { + const { + closeModal, + currentThreats, + isSingleThreat, + actionToConfirm, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + userConnectionNeeded, + siteCredentialsNeeded, + } = useContext( ThreatsModalContext ); + const disabled = userConnectionNeeded || siteCredentialsNeeded; + + const fixerState = useMemo( + () => ( isSingleThreat ? getFixerState( currentThreats[ 0 ].fixer ) : null ), + [ isSingleThreat, currentThreats ] + ); + + const detailedFixerAction = useMemo( + () => ( isSingleThreat ? getDetailedFixerAction( currentThreats[ 0 ] ) : null ), + [ isSingleThreat, currentThreats ] + ); + + const onFixClick = useCallback( () => { + handleFixThreatClick?.( currentThreats ); + closeModal(); + }, [ currentThreats, handleFixThreatClick, closeModal ] ); + + const onIgnoreClick = useCallback( () => { + handleIgnoreThreatClick?.( currentThreats ); + closeModal(); + }, [ currentThreats, handleIgnoreThreatClick, closeModal ] ); + + const onUnignoreClick = useCallback( () => { + handleUnignoreThreatClick?.( currentThreats ); + closeModal(); + }, [ currentThreats, handleUnignoreThreatClick, closeModal ] ); + + if ( + isSingleThreat && + ( ! currentThreats[ 0 ]?.status || currentThreats[ 0 ].status === 'fixed' ) + ) { + return null; + } + + return ( +
    + { isSingleThreat && } +
    + { isSingleThreat ? ( + <> + { currentThreats[ 0 ]?.status === 'ignored' && ( + + ) } + { currentThreats[ 0 ]?.status === 'current' && ( + <> + { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( + + ) } + { currentThreats[ 0 ]?.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( + + ) } + + ) } + + ) : ( + + ) } +
    +
    + ); +}; + +export default ThreatActions; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx similarity index 64% rename from projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx rename to projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 630701a351335..750b103a6669a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -1,13 +1,16 @@ import { __ } from '@wordpress/i18n'; import { useContext } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; +import Text from '../text'; +import ThreatSeverityBadge from '../threat-severity-badge'; +import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatIgnoreDetails from './threat-ignore-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; -import { ThreatModalContext } from '.'; +import { ThreatsModalContext } from '.'; /** * ThreatFixConfirmation component @@ -15,14 +18,34 @@ import { ThreatModalContext } from '.'; * @return {JSX.Element} The rendered fix confirmation. */ const ThreatFixConfirmation = () => { - const { actionToConfirm, userConnectionNeeded, siteCredentialsNeeded, handleUpgradeClick } = - useContext( ThreatModalContext ); + const { + currentThreats, + isSingleThreat, + // actionToConfirm, + userConnectionNeeded, + siteCredentialsNeeded, + handleUpgradeClick, + } = useContext( ThreatsModalContext ); + return ( <> - - - { [ 'all', 'fix' ].includes( actionToConfirm ) && } - { [ 'all', 'ignore' ].includes( actionToConfirm ) && } + { currentThreats.map( ( threat, index ) => ( +
    + { ! isSingleThreat && ( + <> +
    + { threat.title } + { !! threat.severity && } +
    + + ) } + + { /* // TODO: return early in these subcomponents when threat.status is missing - look back at the original logic */ } + { isSingleThreat && } + { isSingleThreat && } +
    + ) ) } + { isSingleThreat && } { siteCredentialsNeeded && userConnectionNeeded && ( { - const { threat } = useContext( ThreatModalContext ); - +const ThreatFixDetails = ( { threat }: { threat: Threat } ): JSX.Element => { const title = useMemo( () => { if ( threat.status === 'fixed' ) { return __( 'How did Jetpack fix it?', 'jetpack-components' ); diff --git a/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx b/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx similarity index 84% rename from projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx rename to projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx index 85f2d730f9fc3..fbf3d85ca040e 100644 --- a/projects/js-packages/components/components/threat-modal/threat-ignore-details.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx @@ -3,14 +3,10 @@ import { __ } from '@wordpress/i18n'; import { useContext } from 'react'; import { Text, Button, getRedirectUrl } from '@automattic/jetpack-components'; import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; +import { ThreatsModalContext } from '.'; const ThreatIgnoreDetails = () => { - const { threat, isSupportedEnvironment } = useContext( ThreatModalContext ); - - if ( ! threat?.status || [ 'ignored', 'fixed' ].includes( threat.status ) ) { - return null; - } + const { isSupportedEnvironment } = useContext( ThreatsModalContext ); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); diff --git a/projects/js-packages/components/components/threat-modal/threat-notice.tsx b/projects/js-packages/components/components/threats-modal/threat-notice.tsx similarity index 93% rename from projects/js-packages/components/components/threat-modal/threat-notice.tsx rename to projects/js-packages/components/components/threats-modal/threat-notice.tsx index b48debe6de1ff..60807b351f1f4 100644 --- a/projects/js-packages/components/components/threat-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-notice.tsx @@ -4,7 +4,7 @@ import { Icon, warning } from '@wordpress/icons'; import { useContext } from 'react'; import { Text, Button } from '@automattic/jetpack-components'; import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; +import { ThreatsModalContext } from '.'; /** * ThreatNotice component @@ -29,18 +29,13 @@ const ThreatNotice = ( { showActions?: boolean; } ): JSX.Element => { const { - threat, userConnectionNeeded, userIsConnecting, handleConnectUser, siteCredentialsNeeded, credentialsRedirectUrl, credentialsIsFetching, - } = useContext( ThreatModalContext ); - - if ( ! threat?.status || threat.status === 'fixed' ) { - return null; - } + } = useContext( ThreatsModalContext ); return ( { - const { threat } = useContext( ThreatModalContext ); - +const ThreatSummary = ( { threat }: { threat: Threat } ): JSX.Element => { return (
    { !! threat.description && { threat.description } } diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threats-modal/threat-technical-details.tsx similarity index 87% rename from projects/js-packages/components/components/threat-modal/threat-technical-details.tsx rename to projects/js-packages/components/components/threats-modal/threat-technical-details.tsx index e2b7c5caa70eb..0b9e14ffa3896 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-technical-details.tsx @@ -1,20 +1,21 @@ +import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; -import { useState, useCallback, useContext } from 'react'; +import { useState, useCallback } from 'react'; import { Text, Button } from '@automattic/jetpack-components'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; /** * ThreatTechnicalDetails component * + * @param {object} props - The props. + * @param {Threat} props.threat - The threat. + * * @return {JSX.Element | null} The rendered technical details or null if no details are available. */ -const ThreatTechnicalDetails = (): JSX.Element => { - const { threat } = useContext( ThreatModalContext ); - +const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { const [ open, setOpen ] = useState( false ); let toggleContent = __( 'Show the technical details', 'jetpack-components' ); diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index 6df50ee7fdb61..6850af7b3dc4c 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ThreatsModal } from './components/threats-modal'; export { default as ShieldIcon } from './components/shield-icon'; export { default as ScanReport } from './components/scan-report'; export { default as Text, H2, H3, Title } from './components/text'; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index ea313bbacc1ce..8adad2cb0df41 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,4 +1,12 @@ -import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { + Text, + Button, + useBreakpointMatch, + ThreatsModal, + getRedirectUrl, +} from '@automattic/jetpack-components'; +import { useConnection } from '@automattic/jetpack-connection'; +import { type Threat } from '@automattic/jetpack-scan'; import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; @@ -7,10 +15,12 @@ import { useCallback, useState, useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; +import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useUnIgnoreThreatMutation from '../../data/scan/use-unignore-threat-mutation'; +import useCredentialsQuery from '../../data/use-credentials-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; @@ -19,7 +29,6 @@ import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' | 'large' } ) => { const { recordEvent } = useAnalyticsTracks(); const { hasPlan, upgradePlan } = usePlan(); - const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); @@ -81,15 +90,49 @@ const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' } } - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; + const { siteSuffix, blogID } = window.jetpackProtectInitialState; + const [ showModal, setShowModal ] = useState( false ); + const { wafSupported } = useWafData(); + + const { fixThreats } = useFixers(); + const ignoreThreatMutation = useIgnoreThreatMutation(); + const unignoreThreatMutation = useUnIgnoreThreatMutation(); + + const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); + const { isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser } = useConnection( + { + redirectUri: 'admin.php?page=jetpack-protect', + from: 'scan', + autoTrigger: false, + skipUserConnection: false, + skipPricingPage: true, + } + ); + + const handleFixClick = useCallback( + async ( threats: Threat[] ) => { + await fixThreats( [ threats[ 0 ].id as number ] ); + }, + [ fixThreats ] + ); + + const handleIgnoreClick = useCallback( + async ( threats: Threat[] ) => { + await ignoreThreatMutation.mutateAsync( threats[ 0 ].id ); + }, + [ ignoreThreatMutation ] + ); + + const handleUnignoreClick = useCallback( + async ( threats: Threat[] ) => { + await unignoreThreatMutation.mutateAsync( threats[ 0 ].id ); + }, + [ unignoreThreatMutation ] + ); + + const toggleModal = useCallback( () => { + setShowModal( ! showModal ); + }, [ showModal ] ); if ( scanning ) { return ; @@ -163,7 +206,7 @@ const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' { fixableList.length > 0 && ( <>
    -
    + { showModal && ( + + ) }
    ) ) } - { isSingleThreat && } + { isSingleThreat && currentThreats[ 0 ].status && } { siteCredentialsNeeded && userConnectionNeeded && ( Date: Thu, 19 Dec 2024 11:40:22 -0800 Subject: [PATCH 138/290] Optimize --- .../threats-modal/threat-fix-confirmation.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index bbe41aae21bdf..f29dad0312d75 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -32,16 +32,18 @@ const ThreatFixConfirmation = () => { { currentThreats.map( ( threat, index ) => (
    { ! isSingleThreat && ( +
    + { threat.title } + { !! threat.severity && } +
    + ) } + + { isSingleThreat && ( <> -
    - { threat.title } - { !! threat.severity && } -
    + + ) } - - { isSingleThreat && } - { isSingleThreat && }
    ) ) } { isSingleThreat && currentThreats[ 0 ].status && } From 7d8b77aa4a4a77299ec0e9835bd1ba8e135947b6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 11:45:28 -0800 Subject: [PATCH 139/290] Reapply actionToConfirm logic --- .../threats-modal/threat-fix-confirmation.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index f29dad0312d75..19577aa77b2bf 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -21,7 +21,7 @@ const ThreatFixConfirmation = () => { const { currentThreats, isSingleThreat, - // actionToConfirm, + actionToConfirm, userConnectionNeeded, siteCredentialsNeeded, handleUpgradeClick, @@ -41,12 +41,16 @@ const ThreatFixConfirmation = () => { { isSingleThreat && ( <> - + { [ 'all', 'fix' ].includes( actionToConfirm ) && ( + + ) } ) }
    ) ) } - { isSingleThreat && currentThreats[ 0 ].status && } + { isSingleThreat && + currentThreats[ 0 ].status && + [ 'all', 'ignore' ].includes( actionToConfirm ) && } { siteCredentialsNeeded && userConnectionNeeded && ( Date: Thu, 19 Dec 2024 12:20:23 -0800 Subject: [PATCH 140/290] Improvements and fixes --- .../threats-modal/threat-fix-confirmation.tsx | 9 ++------- .../threats-modal/threat-fix-details.tsx | 10 ++++++++-- .../threats-modal/threat-ignore-details.tsx | 14 +++++++++++--- .../components/threats-modal/threat-notice.tsx | 9 +++++++++ 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 19577aa77b2bf..85dba25fba1e4 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -21,7 +21,6 @@ const ThreatFixConfirmation = () => { const { currentThreats, isSingleThreat, - actionToConfirm, userConnectionNeeded, siteCredentialsNeeded, handleUpgradeClick, @@ -41,16 +40,12 @@ const ThreatFixConfirmation = () => { { isSingleThreat && ( <> - { [ 'all', 'fix' ].includes( actionToConfirm ) && ( - - ) } + + ) }
    ) ) } - { isSingleThreat && - currentThreats[ 0 ].status && - [ 'all', 'ignore' ].includes( actionToConfirm ) && } { siteCredentialsNeeded && userConnectionNeeded && ( { + const { actionToConfirm } = useContext( ThreatsModalContext ); + const title = useMemo( () => { if ( threat.status === 'fixed' ) { return __( 'How did Jetpack fix it?', 'jetpack-components' ); @@ -39,7 +42,10 @@ const ThreatFixDetails = ( { threat }: { threat: Threat } ): JSX.Element => { return getFixerDescription( threat ); }, [ threat ] ); - if ( ! threat.fixable && ! threat.fixedIn ) { + if ( + ( ! threat.fixable && ! threat.fixedIn ) || + [ 'ignore', 'unignore' ].includes( actionToConfirm ) + ) { return null; } diff --git a/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx b/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx index fbf3d85ca040e..89792276ed10f 100644 --- a/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-ignore-details.tsx @@ -1,3 +1,4 @@ +import { type Threat } from '@automattic/jetpack-scan'; import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useContext } from 'react'; @@ -5,11 +6,18 @@ import { Text, Button, getRedirectUrl } from '@automattic/jetpack-components'; import styles from './styles.module.scss'; import { ThreatsModalContext } from '.'; -const ThreatIgnoreDetails = () => { - const { isSupportedEnvironment } = useContext( ThreatsModalContext ); +const ThreatIgnoreDetails = ( { threat }: { threat: Threat } ) => { + const { actionToConfirm, isSupportedEnvironment } = useContext( ThreatsModalContext ); - const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + if ( + ! threat.status || + [ 'fixed', 'ignored' ].includes( threat.status ) || + [ 'fix', 'unignore' ].includes( actionToConfirm ) + ) { + return null; + } + const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); return (
    diff --git a/projects/js-packages/components/components/threats-modal/threat-notice.tsx b/projects/js-packages/components/components/threats-modal/threat-notice.tsx index 60807b351f1f4..1d4c7a3ac21d9 100644 --- a/projects/js-packages/components/components/threats-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-notice.tsx @@ -29,6 +29,8 @@ const ThreatNotice = ( { showActions?: boolean; } ): JSX.Element => { const { + currentThreats, + isSingleThreat, userConnectionNeeded, userIsConnecting, handleConnectUser, @@ -37,6 +39,13 @@ const ThreatNotice = ( { credentialsIsFetching, } = useContext( ThreatsModalContext ); + if ( + currentThreats.every( threat => ! threat?.status ) || + ( isSingleThreat && currentThreats[ 0 ].status === 'fixed' ) + ) { + return null; + } + return ( Date: Thu, 19 Dec 2024 12:56:01 -0800 Subject: [PATCH 141/290] Update copy --- .../js-packages/components/components/threats-modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index 29d0700d9d7d6..544438164829c 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -95,7 +95,7 @@ export default function ThreatsModal( { ) }
    ) : ( - { 'Auto-fixable threats' } + { 'Fix all threats' } ) }
    } From 244ff4cf19f568c58fc30dfc43f855e966c02ecb Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 13:00:34 -0800 Subject: [PATCH 142/290] Add credentials status polling --- .../routes/scan/scan-admin-section-hero.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 8adad2cb0df41..c539710a86165 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -7,14 +7,16 @@ import { } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { type Threat } from '@automattic/jetpack-scan'; +import { useQueryClient } from '@tanstack/react-query'; import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import clsx from 'clsx'; -import { useCallback, useState, useMemo } from 'react'; +import { useCallback, useState, useMemo, useEffect } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; +import { QUERY_CREDENTIALS_KEY } from '../../constants'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useUnIgnoreThreatMutation from '../../data/scan/use-unignore-threat-mutation'; @@ -32,6 +34,7 @@ const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const queryClient = useQueryClient(); const getScan = useCallback( () => { recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); @@ -134,6 +137,22 @@ const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' setShowModal( ! showModal ); }, [ showModal ] ); + /** + * Poll credentials as long as the modal is open. + */ + useEffect( () => { + if ( ! showModal ) { + return; + } + const interval = setInterval( () => { + if ( ! credentials || credentials.length === 0 ) { + queryClient.invalidateQueries( { queryKey: [ QUERY_CREDENTIALS_KEY ] } ); + } + }, 5_000 ); + + return () => clearInterval( interval ); + }, [ showModal, queryClient, credentials ] ); + if ( scanning ) { return ; } From 6368dad6c567fbb27806bca55f877fc3bc40bca2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 14:36:52 -0800 Subject: [PATCH 143/290] Reorg and remove unused Protect modals --- .../js/components/credentials-gate/README.md | 17 ---- .../js/components/credentials-gate/index.jsx | 35 -------- .../credentials-gate/styles.module.scss | 9 -- .../credentials-needed-modal/index.jsx | 83 ------------------ .../styles.module.scss | 4 - .../fix-all-threats-modal/index.jsx | 84 ------------------ .../fix-all-threats-modal/styles.module.scss | 8 -- .../js/components/fix-threat-modal/index.jsx | 56 ------------ .../fix-threat-modal/styles.module.scss | 8 -- .../components/ignore-threat-modal/index.jsx | 86 ------------------- .../ignore-threat-modal/styles.module.scss | 40 --------- .../protect/src/js/components/modal/index.jsx | 10 --- .../unignore-threat-modal/index.jsx | 68 --------------- .../unignore-threat-modal/styles.module.scss | 40 --------- .../components/user-connection-gate/index.jsx | 14 --- .../user-connection-gate/styles.module.scss | 9 -- .../user-connection-needed-modal/index.jsx | 66 -------------- .../styles.module.scss | 4 - .../routes/scan/scan-admin-section-hero.tsx | 79 ++++++++--------- 19 files changed, 40 insertions(+), 680 deletions(-) delete mode 100644 projects/plugins/protect/src/js/components/credentials-gate/README.md delete mode 100644 projects/plugins/protect/src/js/components/credentials-gate/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/credentials-gate/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/credentials-needed-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/credentials-needed-modal/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/fix-all-threats-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/fix-all-threats-modal/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/fix-threat-modal/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/ignore-threat-modal/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/user-connection-gate/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/user-connection-gate/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/user-connection-needed-modal/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/user-connection-needed-modal/styles.module.scss diff --git a/projects/plugins/protect/src/js/components/credentials-gate/README.md b/projects/plugins/protect/src/js/components/credentials-gate/README.md deleted file mode 100644 index 2d3b72409c6c6..0000000000000 --- a/projects/plugins/protect/src/js/components/credentials-gate/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Credentials Gate - -The `` component blocks the rendering of its contents when the current site does not have credentials shared with Jetpack. - -When credentials are unavailable, the `` component is rendered instead. - -## Usage - -```jsx -import CredentialsGate from './components/credentials-gate'; - -const MyCredentialedCompnent = () = ( - -

    Only users with credentials will see this message.

    -
    -); -``` \ No newline at end of file diff --git a/projects/plugins/protect/src/js/components/credentials-gate/index.jsx b/projects/plugins/protect/src/js/components/credentials-gate/index.jsx deleted file mode 100644 index d93e99bdc17d3..0000000000000 --- a/projects/plugins/protect/src/js/components/credentials-gate/index.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Spinner } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import useCredentialsQuery from '../../data/use-credentials-query'; -import CredentialsNeededModal from '../credentials-needed-modal'; -import styles from './styles.module.scss'; - -const CredentialsGate = ( { children } ) => { - const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); - - if ( credentialsIsFetching ) { - return ( -
    - -

    - { __( 'Checking credentials…', 'jetpack-protect' ) } -

    -
    - ); - } - - if ( ! credentials || credentials.length === 0 ) { - return ; - } - - return children; -}; - -export default CredentialsGate; diff --git a/projects/plugins/protect/src/js/components/credentials-gate/styles.module.scss b/projects/plugins/protect/src/js/components/credentials-gate/styles.module.scss deleted file mode 100644 index 12d6afe939dc3..0000000000000 --- a/projects/plugins/protect/src/js/components/credentials-gate/styles.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.loading { - text-align: center; - padding-top: calc( var( --spacing-base ) * 6 ); // 48px - padding-bottom: calc( var( --spacing-base ) * 6 ); // 48px - - &__message { - margin-bottom: 0; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/components/credentials-needed-modal/index.jsx b/projects/plugins/protect/src/js/components/credentials-needed-modal/index.jsx deleted file mode 100644 index fae5e28633d6e..0000000000000 --- a/projects/plugins/protect/src/js/components/credentials-needed-modal/index.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Button, Text, getRedirectUrl } from '@automattic/jetpack-components'; -import { useQueryClient } from '@tanstack/react-query'; -import { __ } from '@wordpress/i18n'; -import { useEffect } from 'react'; -import { QUERY_CREDENTIALS_KEY } from '../../constants'; -import useCredentialsQuery from '../../data/use-credentials-query'; -import useModal from '../../hooks/use-modal'; -import Notice from '../notice'; -import styles from './styles.module.scss'; - -const CredentialsNeededModal = () => { - const queryClient = useQueryClient(); - const { setModal } = useModal(); - const { data: credentials } = useCredentialsQuery(); - const { siteSuffix, blogID } = window.jetpackProtectInitialState; - - const handleCancelClick = () => { - return event => { - event.preventDefault(); - setModal( { type: null } ); - }; - }; - - /** - * Poll credentials as long as the modal is open. - */ - useEffect( () => { - const interval = setInterval( () => { - if ( ! credentials || credentials.length === 0 ) { - queryClient.invalidateQueries( { queryKey: [ QUERY_CREDENTIALS_KEY ] } ); - } - }, 5_000 ); - - return () => clearInterval( interval ); - }, [ queryClient, credentials ] ); - - return ( - <> - - { __( 'Site credentials needed', 'jetpack-protect' ) } - - - - - - { __( - 'Your server credentials allow Jetpack Protect to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.', - 'jetpack-protect' - ) } - - - - { __( - 'Once you’ve entered server credentials, Jetpack Protect will be fixing the selected threats.', - 'jetpack-protect' - ) } - - -
    - - -
    - - ); -}; - -export default CredentialsNeededModal; diff --git a/projects/plugins/protect/src/js/components/credentials-needed-modal/styles.module.scss b/projects/plugins/protect/src/js/components/credentials-needed-modal/styles.module.scss deleted file mode 100644 index 6b3271908224a..0000000000000 --- a/projects/plugins/protect/src/js/components/credentials-needed-modal/styles.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/components/fix-all-threats-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-all-threats-modal/index.jsx deleted file mode 100644 index 68ef496a610ff..0000000000000 --- a/projects/plugins/protect/src/js/components/fix-all-threats-modal/index.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Button, Text } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { useCallback, useState } from 'react'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import CredentialsGate from '../credentials-gate'; -import ThreatFixHeader from '../threat-fix-header'; -import UserConnectionGate from '../user-connection-gate'; -import styles from './styles.module.scss'; - -const FixAllThreatsModal = ( { threatList = [] } ) => { - const { setModal } = useModal(); - const { fixThreats, isLoading: isFixersLoading } = useFixers(); - - const [ threatIds, setThreatIds ] = useState( threatList.map( ( { id } ) => parseInt( id ) ) ); - - const handleCancelClick = useCallback( - event => { - event.preventDefault(); - setModal( { type: null } ); - }, - [ setModal ] - ); - - const handleFixClick = useCallback( - async event => { - event.preventDefault(); - - await fixThreats( threatIds ); - setModal( { type: null } ); - }, - [ fixThreats, setModal, threatIds ] - ); - - const handleCheckboxClick = useCallback( - ( checked, threat ) => { - if ( ! checked ) { - setThreatIds( threatIds.filter( id => id !== threat.id ) ); - } else { - setThreatIds( [ ...threatIds, threat.id ] ); - } - }, - [ threatIds ] - ); - - return ( - - - - { __( 'Fix all threats', 'jetpack-protect' ) } - - - { __( 'Jetpack will be fixing the selected threats:', 'jetpack-protect' ) } - - -
    - { threatList.map( threat => ( - - ) ) } -
    - -
    - - -
    -
    -
    - ); -}; - -export default FixAllThreatsModal; diff --git a/projects/plugins/protect/src/js/components/fix-all-threats-modal/styles.module.scss b/projects/plugins/protect/src/js/components/fix-all-threats-modal/styles.module.scss deleted file mode 100644 index ab09cfc81f47a..0000000000000 --- a/projects/plugins/protect/src/js/components/fix-all-threats-modal/styles.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.list { - margin-bottom: calc( var( --spacing-base ) * 5 ); // 40px -} - -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx deleted file mode 100644 index cbb49498c353f..0000000000000 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Button, Text } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import CredentialsGate from '../credentials-gate'; -import ThreatFixHeader from '../threat-fix-header'; -import UserConnectionGate from '../user-connection-gate'; -import styles from './styles.module.scss'; - -const FixThreatModal = ( { threat } ) => { - const { setModal } = useModal(); - const { fixThreats, isLoading: isFixersLoading } = useFixers(); - - const handleCancelClick = () => { - return event => { - event.preventDefault(); - setModal( { type: null } ); - }; - }; - - const handleFixClick = () => { - return async event => { - event.preventDefault(); - await fixThreats( [ threat.id ] ); - setModal( { type: null } ); - }; - }; - - return ( - - - - { __( 'Fix Threat', 'jetpack-protect' ) } - - - { __( 'Jetpack will be fixing the selected threat:', 'jetpack-protect' ) } - - -
    - -
    - -
    - - -
    -
    -
    - ); -}; - -export default FixThreatModal; diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/styles.module.scss b/projects/plugins/protect/src/js/components/fix-threat-modal/styles.module.scss deleted file mode 100644 index ab09cfc81f47a..0000000000000 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/styles.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.list { - margin-bottom: calc( var( --spacing-base ) * 5 ); // 40px -} - -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx deleted file mode 100644 index 1d6f4457b39a4..0000000000000 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; -import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; -import { Icon } from '@wordpress/components'; -import { createInterpolateElement, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; -import useModal from '../../hooks/use-modal'; -import useWafData from '../../hooks/use-waf-data'; -import UserConnectionGate from '../user-connection-gate'; -import styles from './styles.module.scss'; - -const IgnoreThreatModal = ( { threat } ) => { - const { wafSupported } = useWafData(); - const { setModal } = useModal(); - const ignoreThreatMutation = useIgnoreThreatMutation(); - const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); - const icon = getThreatIcon( threat ); - - const [ isIgnoring, setIsIgnoring ] = useState( false ); - - const handleCancelClick = () => { - return event => { - event.preventDefault(); - setModal( { type: null } ); - }; - }; - - const handleIgnoreClick = () => { - return async event => { - event.preventDefault(); - setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( threat.id ); - setModal( { type: null } ); - setIsIgnoring( false ); - }; - }; - - return ( - - - { __( 'Do you really want to ignore this threat?', 'jetpack-protect' ) } - - { __( 'Jetpack will ignore the threat:', 'jetpack-protect' ) } - -
    - -
    - - { getThreatSubtitle( threat ) } - - { threat.title } -
    -
    - -
    -
    - - - { __( - 'By choosing to ignore this threat, you acknowledge that you have reviewed the detected code. You are accepting the risks of maintaining a potentially malicious or vulnerable file on your site.', - 'jetpack-protect' - ) }{ ' ' } - { wafSupported && - createInterpolateElement( - __( - 'If you are unsure, please request an estimate with Codeable.', - 'jetpack-protect' - ), - { - codeableLink: - -
    - - ); -}; - -export default IgnoreThreatModal; diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/styles.module.scss b/projects/plugins/protect/src/js/components/ignore-threat-modal/styles.module.scss deleted file mode 100644 index d936392c844e5..0000000000000 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/styles.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -.threat { - border: 1px solid var( --jp-gray ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - - &__icon { - margin-right: calc( var( --spacing-base ) * 2 ); // 16px - min-width: 24px; - } - - &__summary { - width: 100%; - } - - &__summary__label { - font-size: 18px; - line-height: 24px; - font-weight: 600; - margin-bottom: 0; - } - - &__summary__title { - font-size: 14px; - line-height: 21px; - color: var( --jp-gray-80 ); - } - - &__severity { - align-self: center; - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - margin-right: var( --spacing-base ); // 8px - } -} - -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/components/modal/index.jsx b/projects/plugins/protect/src/js/components/modal/index.jsx index 2d8728964fb03..4d53ef95212c2 100644 --- a/projects/plugins/protect/src/js/components/modal/index.jsx +++ b/projects/plugins/protect/src/js/components/modal/index.jsx @@ -1,20 +1,10 @@ import { __ } from '@wordpress/i18n'; import { close as closeIcon, Icon } from '@wordpress/icons'; import useModal from '../../hooks/use-modal'; -import CredentialsNeededModal from '../credentials-needed-modal'; -import FixAllThreatsModal from '../fix-all-threats-modal'; -import FixThreatModal from '../fix-threat-modal'; -import IgnoreThreatModal from '../ignore-threat-modal'; import StandaloneModeModal from '../standalone-mode-modal'; -import UnignoreThreatModal from '../unignore-threat-modal'; import styles from './styles.module.scss'; const MODAL_COMPONENTS = { - IGNORE_THREAT: IgnoreThreatModal, - UNIGNORE_THREAT: UnignoreThreatModal, - FIX_THREAT: FixThreatModal, - FIX_ALL_THREATS: FixAllThreatsModal, - CREDENTIALS_NEEDED: CredentialsNeededModal, STANDALONE_MODE: StandaloneModeModal, }; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx deleted file mode 100644 index 7f1ef3652bb85..0000000000000 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; -import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; -import { useState } from 'react'; -import useUnIgnoreThreatMutation from '../../data/scan/use-unignore-threat-mutation'; -import useModal from '../../hooks/use-modal'; -import UserConnectionGate from '../user-connection-gate'; -import styles from './styles.module.scss'; - -const UnignoreThreatModal = ( { threat } ) => { - const { setModal } = useModal(); - - const icon = getThreatIcon( threat ); - - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const unignoreThreatMutation = useUnIgnoreThreatMutation(); - - const handleCancelClick = () => { - return event => { - event.preventDefault(); - setModal( { type: null } ); - }; - }; - - const handleUnignoreClick = () => { - return async event => { - event.preventDefault(); - setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( threat.id ); - setModal( { type: null } ); - setIsUnignoring( false ); - }; - }; - - return ( - - - { __( 'Do you really want to unignore this threat?', 'jetpack-protect' ) } - - { __( 'Jetpack will unignore the threat:', 'jetpack-protect' ) } - -
    - -
    - - { getThreatSubtitle( threat ) } - - { threat.title } -
    -
    - -
    -
    - -
    - - -
    -
    - ); -}; - -export default UnignoreThreatModal; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss b/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss deleted file mode 100644 index d936392c844e5..0000000000000 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -.threat { - border: 1px solid var( --jp-gray ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - - &__icon { - margin-right: calc( var( --spacing-base ) * 2 ); // 16px - min-width: 24px; - } - - &__summary { - width: 100%; - } - - &__summary__label { - font-size: 18px; - line-height: 24px; - font-weight: 600; - margin-bottom: 0; - } - - &__summary__title { - font-size: 14px; - line-height: 21px; - color: var( --jp-gray-80 ); - } - - &__severity { - align-self: center; - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - margin-right: var( --spacing-base ); // 8px - } -} - -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/components/user-connection-gate/index.jsx b/projects/plugins/protect/src/js/components/user-connection-gate/index.jsx deleted file mode 100644 index cda8c2fc5aad5..0000000000000 --- a/projects/plugins/protect/src/js/components/user-connection-gate/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useConnection } from '@automattic/jetpack-connection'; -import UserConnectionNeededModal from '../user-connection-needed-modal'; - -const UserConnectionGate = ( { children } ) => { - const { isUserConnected, hasConnectedOwner } = useConnection(); - - if ( ! isUserConnected || ! hasConnectedOwner ) { - return ; - } - - return children; -}; - -export default UserConnectionGate; diff --git a/projects/plugins/protect/src/js/components/user-connection-gate/styles.module.scss b/projects/plugins/protect/src/js/components/user-connection-gate/styles.module.scss deleted file mode 100644 index 12d6afe939dc3..0000000000000 --- a/projects/plugins/protect/src/js/components/user-connection-gate/styles.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.loading { - text-align: center; - padding-top: calc( var( --spacing-base ) * 6 ); // 48px - padding-bottom: calc( var( --spacing-base ) * 6 ); // 48px - - &__message { - margin-bottom: 0; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/components/user-connection-needed-modal/index.jsx b/projects/plugins/protect/src/js/components/user-connection-needed-modal/index.jsx deleted file mode 100644 index 47d551248eea5..0000000000000 --- a/projects/plugins/protect/src/js/components/user-connection-needed-modal/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Button, Text } from '@automattic/jetpack-components'; -import { useConnection } from '@automattic/jetpack-connection'; -import { __ } from '@wordpress/i18n'; -import useModal from '../../hooks/use-modal'; -import Notice from '../notice'; -import styles from './styles.module.scss'; - -const UserConnectionNeededModal = () => { - const { setModal } = useModal(); - const { userIsConnecting, handleConnectUser } = useConnection( { - redirectUri: 'admin.php?page=jetpack-protect', - } ); - - const handleCancelClick = () => { - return event => { - event.preventDefault(); - setModal( { type: null } ); - }; - }; - - return ( - <> - - { __( 'User connection needed', 'jetpack-protect' ) } - - - - - - { __( - 'A user connection provides Jetpack Protect the access necessary to perform these tasks.', - 'jetpack-protect' - ) } - - - - { __( - 'Once you’ve secured a user connection, all Jetpack Protect features will be available for use.', - 'jetpack-protect' - ) } - - -
    - - -
    - - ); -}; - -export default UserConnectionNeededModal; diff --git a/projects/plugins/protect/src/js/components/user-connection-needed-modal/styles.module.scss b/projects/plugins/protect/src/js/components/user-connection-needed-modal/styles.module.scss deleted file mode 100644 index 6b3271908224a..0000000000000 --- a/projects/plugins/protect/src/js/components/user-connection-needed-modal/styles.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.footer { - display: flex; - justify-content: space-between; -} diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index c539710a86165..91922be65ffd1 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -29,41 +29,46 @@ import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' | 'large' } ) => { + const [ isSm ] = useBreakpointMatch( 'sm' ); + const { recordEvent } = useAnalyticsTracks(); const { hasPlan, upgradePlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { data: status } = useScanStatusQuery(); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const queryClient = useQueryClient(); - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); + const { wafSupported, globalStats } = useWafData(); + const { data: status } = useScanStatusQuery(); - const { globalStats } = useWafData(); - const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); - const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) - ? '50,000' - : totalVulnerabilities.toLocaleString(); + const { fixThreats, isThreatFixInProgress, isThreatFixStale } = useFixers(); + const ignoreThreatMutation = useIgnoreThreatMutation(); + const unignoreThreatMutation = useUnIgnoreThreatMutation(); - const numThreats = status.threats.length; + const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); + const { isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser } = useConnection( + { + redirectUri: 'admin.php?page=jetpack-protect', + from: 'scan', + autoTrigger: false, + skipUserConnection: false, + skipPricingPage: true, + } + ); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return status.threats.filter( threat => { - const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); + const [ showModal, setShowModal ] = useState( false ); + + const { siteSuffix, blogID } = window.jetpackProtectInitialState; + + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); const scanning = isScanInProgress( status ); + const numThreats = status.threats.length; let lastCheckedLocalTimestamp = null; if ( status.lastChecked ) { @@ -93,24 +98,20 @@ const ScanAdminSectionHero: React.FC = ( { size = 'normal' }: { size?: 'normal' } } - const { siteSuffix, blogID } = window.jetpackProtectInitialState; - const [ showModal, setShowModal ] = useState( false ); - const { wafSupported } = useWafData(); - - const { fixThreats } = useFixers(); - const ignoreThreatMutation = useIgnoreThreatMutation(); - const unignoreThreatMutation = useUnIgnoreThreatMutation(); + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); - const { data: credentials, isLoading: credentialsIsFetching } = useCredentialsQuery(); - const { isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser } = useConnection( - { - redirectUri: 'admin.php?page=jetpack-protect', - from: 'scan', - autoTrigger: false, - skipUserConnection: false, - skipPricingPage: true, - } - ); + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); const handleFixClick = useCallback( async ( threats: Threat[] ) => { From 5e22d9e8ee17fb91486e9c59970763786855cc40 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 19:03:50 -0800 Subject: [PATCH 144/290] Align with Protect bulk modal structure, styling and functionality --- .../threats-modal/styles.module.scss | 38 ++++++++++ .../threats-modal/threat-actions.tsx | 38 +++++----- .../threats-modal/threat-fix-confirmation.tsx | 75 +++++++++++++++++-- .../threats-modal/threat-fix-details.tsx | 15 +++- 4 files changed, 138 insertions(+), 28 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index a3cfb182c4eb3..741ccb711daf1 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -24,6 +24,44 @@ } } +.bulk { + border: 1px solid var( --jp-gray-5 ); + padding: calc( var( --spacing-base ) * 3 ); // 24px; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + display: flex; + justify-content: space-between; + align-items: center; + + &__content { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + align-items: center; + } + + &__title { + max-width: 512px; + }; + + &__toggle { + margin-bottom: 0; + } + + &__media { + width: 52px; + height: 52px; + display: flex; + align-items: center; + justify-content: center; + background-color: #EDFFEE; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 4px; + + svg { + fill: var( --jp-black ); + } + } +} + .title { display: flex; align-items: center; diff --git a/projects/js-packages/components/components/threats-modal/threat-actions.tsx b/projects/js-packages/components/components/threats-modal/threat-actions.tsx index 58adcd217a97f..4febc1a1d0e6e 100644 --- a/projects/js-packages/components/components/threats-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-actions.tsx @@ -1,4 +1,4 @@ -import { getFixerState, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { getFixerState, getDetailedFixerAction, type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { useCallback, useContext, useMemo } from 'react'; import { Button } from '@automattic/jetpack-components'; @@ -9,12 +9,14 @@ import { ThreatsModalContext } from '.'; /** * ThreatActions component * + * @param {object} props - The props. + * @param {Threat[]} props.selectedThreats - The selected threats. + * * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. */ -const ThreatActions = (): JSX.Element => { +const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JSX.Element => { const { closeModal, - currentThreats, isSingleThreat, actionToConfirm, handleFixThreatClick, @@ -23,36 +25,36 @@ const ThreatActions = (): JSX.Element => { userConnectionNeeded, siteCredentialsNeeded, } = useContext( ThreatsModalContext ); - const disabled = userConnectionNeeded || siteCredentialsNeeded; + const disabled = userConnectionNeeded || siteCredentialsNeeded || selectedThreats.length === 0; const fixerState = useMemo( - () => ( isSingleThreat ? getFixerState( currentThreats[ 0 ].fixer ) : null ), - [ isSingleThreat, currentThreats ] + () => ( isSingleThreat ? getFixerState( selectedThreats[ 0 ].fixer ) : null ), + [ isSingleThreat, selectedThreats ] ); const detailedFixerAction = useMemo( - () => ( isSingleThreat ? getDetailedFixerAction( currentThreats[ 0 ] ) : null ), - [ isSingleThreat, currentThreats ] + () => ( isSingleThreat ? getDetailedFixerAction( selectedThreats[ 0 ] ) : null ), + [ isSingleThreat, selectedThreats ] ); const onFixClick = useCallback( () => { - handleFixThreatClick?.( currentThreats ); + handleFixThreatClick?.( selectedThreats ); closeModal(); - }, [ currentThreats, handleFixThreatClick, closeModal ] ); + }, [ selectedThreats, handleFixThreatClick, closeModal ] ); const onIgnoreClick = useCallback( () => { - handleIgnoreThreatClick?.( currentThreats ); + handleIgnoreThreatClick?.( selectedThreats ); closeModal(); - }, [ currentThreats, handleIgnoreThreatClick, closeModal ] ); + }, [ selectedThreats, handleIgnoreThreatClick, closeModal ] ); const onUnignoreClick = useCallback( () => { - handleUnignoreThreatClick?.( currentThreats ); + handleUnignoreThreatClick?.( selectedThreats ); closeModal(); - }, [ currentThreats, handleUnignoreThreatClick, closeModal ] ); + }, [ selectedThreats, handleUnignoreThreatClick, closeModal ] ); if ( isSingleThreat && - ( ! currentThreats[ 0 ]?.status || currentThreats[ 0 ].status === 'fixed' ) + ( ! selectedThreats[ 0 ]?.status || selectedThreats[ 0 ].status === 'fixed' ) ) { return null; } @@ -63,7 +65,7 @@ const ThreatActions = (): JSX.Element => {
    { isSingleThreat ? ( <> - { currentThreats[ 0 ]?.status === 'ignored' && ( + { selectedThreats[ 0 ]?.status === 'ignored' && ( ) } - { currentThreats[ 0 ]?.status === 'current' && ( + { selectedThreats[ 0 ]?.status === 'current' && ( <> { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( ) } - { currentThreats[ 0 ]?.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( + { selectedThreats[ 0 ]?.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( @@ -43,32 +58,49 @@ const Base = args => { }, }, { - id: 185869886, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { - fixer: 'rollback', - target: 'January 26, 2024, 6:49 am', - extensionStatus: '', + id: 12345678910, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: Example Plugin (version 1.2.3)', + description: 'This threat has an in-progress auto-fixer.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '1.2.4', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date().toISOString() }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Plugin', + slug: 'example-plugin', + version: '1.2.3', + type: 'plugins', }, - fixer: { status: 'not_started' }, + }, + { + id: 12345678911, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Theme: Example Theme (version 2.2.2)', + description: 'This threat has an in-progress auto-fixer that is taking too long.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '2.22.22', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() }, status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ) : null } @@ -107,9 +139,6 @@ ThreatResult.args = { isUserConnected: true, hasConnectedOwner: true, credentials: [ { type: 'managed', role: 'main', still_valid: true } ], - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, }; export const ThreatsResult = Base.bind( {} ); @@ -117,9 +146,6 @@ ThreatsResult.args = { isUserConnected: true, hasConnectedOwner: true, credentials: [ { type: 'managed', role: 'main', still_valid: true } ], - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, }; export const AdditionalConnectionsNeeded = Base.bind( {} ); @@ -129,9 +155,6 @@ AdditionalConnectionsNeeded.args = { credentials: false, credentialsRedirectUrl: '#', handleConnectUser: () => {}, - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, }; export const UserConnectionNeeded = Base.bind( {} ); @@ -141,9 +164,6 @@ UserConnectionNeeded.args = { handleConnectUser: () => {}, credentials: [ { type: 'managed', role: 'main', still_valid: true } ], credentialsRedirectUrl: '#', - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, }; export const CredentialsNeeded = Base.bind( {} ); @@ -153,7 +173,4 @@ CredentialsNeeded.args = { credentials: false, credentialsIsFetching: false, credentialsRedirectUrl: '#', - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, }; diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index 741ccb711daf1..f1cd8bbc4d696 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -38,8 +38,8 @@ align-items: center; } - &__title { - max-width: 512px; + &__title { + width: 512px; }; &__toggle { From 51d1f89108736ef7a06db6a235d5754bc4bd7d2b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 19:22:52 -0800 Subject: [PATCH 146/290] Add story variations --- .../threats-modal/stories/index.stories.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx index ac376263bb84f..46f4cd3f97002 100644 --- a/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threats-modal/stories/index.stories.tsx @@ -159,6 +159,32 @@ AdditionalConnectionsNeeded.args = { export const UserConnectionNeeded = Base.bind( {} ); UserConnectionNeeded.args = { + currentThreats: [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { + fixer: 'rollback', + target: 'January 26, 2024, 6:49 am', + extensionStatus: '', + }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, From 4d29bfab3133cb4825c04ee044b5578713bf24d2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 19 Dec 2024 19:51:01 -0800 Subject: [PATCH 147/290] Fix mobile styling --- .../components/threats-modal/styles.module.scss | 9 +++------ .../components/threats-modal/threat-notice.tsx | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index f1cd8bbc4d696..a2ed12e79d749 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -39,7 +39,7 @@ } &__title { - width: 512px; + max-width: 512px; }; &__toggle { @@ -99,10 +99,7 @@ &__actions { display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px; - } - - &__action { - margin-top: calc( var( --spacing-base ) * 2 ); // 16px; + flex-wrap: wrap; } } @@ -112,4 +109,4 @@ svg.spinner { width: 20px; margin-left: calc( var( --spacing-base ) / 2 ); // 4px; margin-right: 6px; -} \ No newline at end of file +} diff --git a/projects/js-packages/components/components/threats-modal/threat-notice.tsx b/projects/js-packages/components/components/threats-modal/threat-notice.tsx index 1d4c7a3ac21d9..d9b2a0abc30d8 100644 --- a/projects/js-packages/components/components/threats-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-notice.tsx @@ -62,7 +62,7 @@ const ThreatNotice = ( { { title }
    - { content } + { content } { showActions && (
    { userConnectionNeeded && ( From f2dbed656f236659219a8cc61fc1fdb2c096920b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 06:03:00 -0800 Subject: [PATCH 148/290] Rename vars, centralize shared constants, fix bulk item flex --- pnpm-lock.yaml | 3 ++ .../threats-data-views/constants.ts | 15 ---------- .../components/threats-data-views/index.tsx | 3 +- .../components/threats-modal/index.tsx | 12 ++++---- .../threats-modal/styles.module.scss | 3 +- .../threats-modal/threat-actions.tsx | 27 ++++++++---------- .../threats-modal/threat-fix-confirmation.tsx | 28 ++++--------------- .../threats-modal/threat-notice.tsx | 4 +-- projects/js-packages/scan/package.json | 1 + .../js-packages/scan/src/constants/index.ts | 16 +++++++++++ 10 files changed, 49 insertions(+), 63 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eca957986d25e..73b0aa46de1ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1241,6 +1241,9 @@ importers: '@wordpress/i18n': specifier: 5.14.0 version: 5.14.0 + '@wordpress/icons': + specifier: 10.14.0 + version: 10.14.0(react@18.3.1) '@wordpress/url': specifier: 4.14.0 version: 4.14.0 diff --git a/projects/js-packages/components/components/threats-data-views/constants.ts b/projects/js-packages/components/components/threats-data-views/constants.ts index 40941b43ad032..db8e14733871c 100644 --- a/projects/js-packages/components/components/threats-data-views/constants.ts +++ b/projects/js-packages/components/components/threats-data-views/constants.ts @@ -1,11 +1,4 @@ import { __ } from '@wordpress/i18n'; -import { - code as fileIcon, - color as themeIcon, - plugins as pluginIcon, - shield as shieldIcon, - wordpress as coreIcon, -} from '@wordpress/icons'; export const THREAT_STATUSES: { value: string; label: string; variant?: 'success' | 'warning' }[] = [ @@ -22,14 +15,6 @@ export const THREAT_TYPES = [ { value: '', label: __( 'Unknown', 'jetpack-components' ) }, ]; -export const THREAT_ICONS = { - plugins: pluginIcon, - themes: themeIcon, - core: coreIcon, - file: fileIcon, - default: shieldIcon, -}; - export const THREAT_FIELD_THREAT = 'threat'; export const THREAT_FIELD_TITLE = 'title'; export const THREAT_FIELD_DESCRIPTION = 'description'; diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 7b97cedf42b0a..282ac0461a6dd 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,4 +1,4 @@ -import { getThreatType, type Threat } from '@automattic/jetpack-scan'; +import { getThreatType, type Threat, THREAT_ICONS } from '@automattic/jetpack-scan'; import { type Action, type Field, @@ -35,7 +35,6 @@ import { THREAT_FIELD_THEME, THREAT_FIELD_TITLE, THREAT_FIELD_TYPE, - THREAT_ICONS, THREAT_STATUSES, THREAT_TYPES, } from './constants'; diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index 544438164829c..b37abf5f64fdc 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -9,7 +9,7 @@ import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; currentThreats: Threat[]; - isSingleThreat: boolean; + isBulk: boolean; actionToConfirm: string | null; isSupportedEnvironment: boolean; userConnectionNeeded: boolean; @@ -81,21 +81,21 @@ export default function ThreatsModal( { } & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; - const isSingleThreat = currentThreats.length === 1; + const isBulk = currentThreats.length > 1; return ( - { isSingleThreat ? ( + { isBulk ? ( + { 'Fix all threats' } + ) : (
    { currentThreats[ 0 ].title } { !! currentThreats[ 0 ].severity && ( ) }
    - ) : ( - { 'Fix all threats' } ) }
    } @@ -107,7 +107,7 @@ export default function ThreatsModal( { value={ { closeModal: modalProps.onRequestClose, currentThreats, - isSingleThreat, + isBulk, actionToConfirm, isSupportedEnvironment, userConnectionNeeded, diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index a2ed12e79d749..411880d00fb84 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -27,12 +27,13 @@ .bulk { border: 1px solid var( --jp-gray-5 ); padding: calc( var( --spacing-base ) * 3 ); // 24px; - gap: calc( var( --spacing-base ) * 2 ); // 16px; + gap: calc( var( --spacing-base ) * 3 ); // 24px; display: flex; justify-content: space-between; align-items: center; &__content { + flex: 1; display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px align-items: center; diff --git a/projects/js-packages/components/components/threats-modal/threat-actions.tsx b/projects/js-packages/components/components/threats-modal/threat-actions.tsx index 4febc1a1d0e6e..343399fc96d22 100644 --- a/projects/js-packages/components/components/threats-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-actions.tsx @@ -17,7 +17,7 @@ import { ThreatsModalContext } from '.'; const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JSX.Element => { const { closeModal, - isSingleThreat, + isBulk, actionToConfirm, handleFixThreatClick, handleIgnoreThreatClick, @@ -28,13 +28,13 @@ const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JS const disabled = userConnectionNeeded || siteCredentialsNeeded || selectedThreats.length === 0; const fixerState = useMemo( - () => ( isSingleThreat ? getFixerState( selectedThreats[ 0 ].fixer ) : null ), - [ isSingleThreat, selectedThreats ] + () => ( ! isBulk ? getFixerState( selectedThreats[ 0 ].fixer ) : null ), + [ isBulk, selectedThreats ] ); const detailedFixerAction = useMemo( - () => ( isSingleThreat ? getDetailedFixerAction( selectedThreats[ 0 ] ) : null ), - [ isSingleThreat, selectedThreats ] + () => ( ! isBulk ? getDetailedFixerAction( selectedThreats[ 0 ] ) : null ), + [ isBulk, selectedThreats ] ); const onFixClick = useCallback( () => { @@ -52,18 +52,19 @@ const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JS closeModal(); }, [ selectedThreats, handleUnignoreThreatClick, closeModal ] ); - if ( - isSingleThreat && - ( ! selectedThreats[ 0 ]?.status || selectedThreats[ 0 ].status === 'fixed' ) - ) { + if ( ! isBulk && ( ! selectedThreats[ 0 ]?.status || selectedThreats[ 0 ].status === 'fixed' ) ) { return null; } return (
    - { isSingleThreat && } + { ! isBulk && }
    - { isSingleThreat ? ( + { isBulk ? ( + + ) : ( <> { selectedThreats[ 0 ]?.status === 'ignored' && ( ) }
    diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 8f1f640e7442b..6f8e28167d04d 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -1,13 +1,6 @@ -import { getThreatType, type Threat } from '@automattic/jetpack-scan'; +import { getThreatType, type Threat, THREAT_ICONS } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import { - Icon, - code as fileIcon, - color as themeIcon, - plugins as pluginIcon, - shield as shieldIcon, - wordpress as coreIcon, -} from '@wordpress/icons'; +import { Icon } from '@wordpress/icons'; import { useContext, useState, useCallback, useMemo } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; import useBreakpointMatch from '../layout/use-breakpoint-match'; @@ -23,14 +16,6 @@ import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; import { ThreatsModalContext } from '.'; -export const THREAT_ICONS = { - plugins: pluginIcon, - themes: themeIcon, - core: coreIcon, - file: fileIcon, - default: shieldIcon, -}; - /** * ThreatFixConfirmation component * @@ -41,7 +26,7 @@ const ThreatFixConfirmation = () => { const { currentThreats, - isSingleThreat, + isBulk, userConnectionNeeded, siteCredentialsNeeded, handleUpgradeClick, @@ -70,10 +55,10 @@ const ThreatFixConfirmation = () => { return ( <> - { ! isSingleThreat && { 'Jetpack will be fixing the selected threats:' } } + { isBulk && { 'Jetpack will be fixing the selected threats:' } } { currentThreats.map( ( threat, index ) => (
    - { ! isSingleThreat && ( + { isBulk ? (
    { ! isSm && ( @@ -98,8 +83,7 @@ const ThreatFixConfirmation = () => { onChange={ toggleHandlers[ threat.id ] } />
    - ) } - { isSingleThreat && ( + ) : ( <> diff --git a/projects/js-packages/components/components/threats-modal/threat-notice.tsx b/projects/js-packages/components/components/threats-modal/threat-notice.tsx index d9b2a0abc30d8..112bf99a6fab3 100644 --- a/projects/js-packages/components/components/threats-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-notice.tsx @@ -30,7 +30,7 @@ const ThreatNotice = ( { } ): JSX.Element => { const { currentThreats, - isSingleThreat, + isBulk, userConnectionNeeded, userIsConnecting, handleConnectUser, @@ -41,7 +41,7 @@ const ThreatNotice = ( { if ( currentThreats.every( threat => ! threat?.status ) || - ( isSingleThreat && currentThreats[ 0 ].status === 'fixed' ) + ( ! isBulk && currentThreats[ 0 ].status === 'fixed' ) ) { return null; } diff --git a/projects/js-packages/scan/package.json b/projects/js-packages/scan/package.json index d0e91a3b7e36a..4c337339d8966 100644 --- a/projects/js-packages/scan/package.json +++ b/projects/js-packages/scan/package.json @@ -50,6 +50,7 @@ "@automattic/jetpack-base-styles": "workspace:*", "@wordpress/api-fetch": "7.14.0", "@wordpress/element": "6.14.0", + "@wordpress/icons": "10.14.0", "@wordpress/i18n": "5.14.0", "@wordpress/url": "4.14.0", "debug": "4.3.4", diff --git a/projects/js-packages/scan/src/constants/index.ts b/projects/js-packages/scan/src/constants/index.ts index bccc025dbe2e9..1e6eb19d7cced 100644 --- a/projects/js-packages/scan/src/constants/index.ts +++ b/projects/js-packages/scan/src/constants/index.ts @@ -1,4 +1,20 @@ +import { + code as fileIcon, + color as themeIcon, + plugins as pluginIcon, + shield as shieldIcon, + wordpress as coreIcon, +} from '@wordpress/icons'; + export const CONTACT_SUPPORT_URL = 'https://jetpack.com/contact-support/?rel=support'; /** Once a fixer has been running for this specified amount of time (in ms), it should be considered "stale". */ export const FIXER_IS_STALE_THRESHOLD = 1000 * 60 * 60 * 24; // 24 hours + +export const THREAT_ICONS = { + plugins: pluginIcon, + themes: themeIcon, + core: coreIcon, + file: fileIcon, + default: shieldIcon, +}; From b62ad8a4ce2349d6d84c35c61194a9822e85e0e2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 08:41:31 -0800 Subject: [PATCH 149/290] Optimize --- .../threats-modal/connections-notice.tsx | 57 +++++++++ .../components/threats-modal/index.tsx | 11 +- .../threats-modal/threat-actions.tsx | 97 ++++++++-------- .../threats-modal/threat-fix-confirmation.tsx | 108 ++++++------------ 4 files changed, 149 insertions(+), 124 deletions(-) create mode 100644 projects/js-packages/components/components/threats-modal/connections-notice.tsx diff --git a/projects/js-packages/components/components/threats-modal/connections-notice.tsx b/projects/js-packages/components/components/threats-modal/connections-notice.tsx new file mode 100644 index 0000000000000..ec644bbedc53a --- /dev/null +++ b/projects/js-packages/components/components/threats-modal/connections-notice.tsx @@ -0,0 +1,57 @@ +import { __ } from '@wordpress/i18n'; +import { useContext } from 'react'; +import ThreatNotice from './threat-notice'; +import { ThreatsModalContext } from '.'; + +/** + * ConnectionsNotice component + * + * @return {JSX.Element | null} The rendered connections notice or null if no notice is available. + */ +const ConnectionsNotice = () => { + const { siteCredentialsNeeded, userConnectionNeeded } = useContext( ThreatsModalContext ); + + if ( ! siteCredentialsNeeded && ! userConnectionNeeded ) { + return null; + } + + if ( siteCredentialsNeeded && userConnectionNeeded ) { + return ( + + ); + } + + if ( ! siteCredentialsNeeded && userConnectionNeeded ) { + return ( + + ); + } + + if ( siteCredentialsNeeded && ! userConnectionNeeded ) { + return ( + + ); + } + + return null; +}; + +export default ConnectionsNotice; diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index b37abf5f64fdc..e8d4977f71c67 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -82,6 +82,7 @@ export default function ThreatsModal( { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; const isBulk = currentThreats.length > 1; + const firstThreat = currentThreats[ 0 ]; return ( { 'Fix all threats' } ) : ( -
    - { currentThreats[ 0 ].title } - { !! currentThreats[ 0 ].severity && ( - + <> + { firstThreat.title } + { !! firstThreat.severity && ( + ) } -
    + ) }
    } diff --git a/projects/js-packages/components/components/threats-modal/threat-actions.tsx b/projects/js-packages/components/components/threats-modal/threat-actions.tsx index 343399fc96d22..2c833fc5f3d79 100644 --- a/projects/js-packages/components/components/threats-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-actions.tsx @@ -25,16 +25,18 @@ const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JS userConnectionNeeded, siteCredentialsNeeded, } = useContext( ThreatsModalContext ); + + const firstThreat = selectedThreats[ 0 ]; const disabled = userConnectionNeeded || siteCredentialsNeeded || selectedThreats.length === 0; const fixerState = useMemo( - () => ( ! isBulk ? getFixerState( selectedThreats[ 0 ].fixer ) : null ), - [ isBulk, selectedThreats ] + () => ( ! isBulk ? getFixerState( firstThreat.fixer ) : null ), + [ isBulk, firstThreat ] ); const detailedFixerAction = useMemo( - () => ( ! isBulk ? getDetailedFixerAction( selectedThreats[ 0 ] ) : null ), - [ isBulk, selectedThreats ] + () => ( ! isBulk ? getDetailedFixerAction( firstThreat ) : null ), + [ isBulk, firstThreat ] ); const onFixClick = useCallback( () => { @@ -52,57 +54,56 @@ const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JS closeModal(); }, [ selectedThreats, handleUnignoreThreatClick, closeModal ] ); - if ( ! isBulk && ( ! selectedThreats[ 0 ]?.status || selectedThreats[ 0 ].status === 'fixed' ) ) { + if ( ! isBulk && ( ! firstThreat?.status || firstThreat.status === 'fixed' ) ) { return null; } + const renderBulkActions = () => ( + + ); + + const renderIndividualActions = () => ( + <> + { firstThreat.status === 'ignored' && ( + + ) } + { firstThreat.status === 'current' && ( + <> + { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( + + ) } + { firstThreat.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( + + ) } + + ) } + + ); + return (
    { ! isBulk && }
    - { isBulk ? ( - - ) : ( - <> - { selectedThreats[ 0 ]?.status === 'ignored' && ( - - ) } - { selectedThreats[ 0 ]?.status === 'current' && ( - <> - { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( - - ) } - { selectedThreats[ 0 ]?.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( - - ) } - - ) } - - ) } + { isBulk ? renderBulkActions() : renderIndividualActions() }
    ); diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 6f8e28167d04d..59160613fe787 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -7,11 +7,11 @@ import useBreakpointMatch from '../layout/use-breakpoint-match'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import ToggleControl from '../toggle-control'; +import ConnectionsNotice from './connections-notice'; import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatIgnoreDetails from './threat-ignore-details'; -import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; import { ThreatsModalContext } from '.'; @@ -24,13 +24,7 @@ import { ThreatsModalContext } from '.'; const ThreatFixConfirmation = () => { const [ isSm ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - const { - currentThreats, - isBulk, - userConnectionNeeded, - siteCredentialsNeeded, - handleUpgradeClick, - } = useContext( ThreatsModalContext ); + const { currentThreats, isBulk, handleUpgradeClick } = useContext( ThreatsModalContext ); const [ selectedThreats, setSelectedThreats ] = useState( currentThreats ); @@ -53,73 +47,45 @@ const ThreatFixConfirmation = () => { }, {} ); }, [ currentThreats, handleToggleThreat ] ); + const renderBulkThreat = threat => ( +
    +
    + { ! isSm && ( +
    + +
    + ) } +
    + { threat.title } + +
    +
    + { ! isSm && !! threat.severity && } + selectedThreat.id === threat.id ) } + onChange={ toggleHandlers[ threat.id ] } + /> +
    + ); + + const renderIndividualThreat = threat => ( +
    + + + + +
    + ); + return ( <> { isBulk && { 'Jetpack will be fixing the selected threats:' } } - { currentThreats.map( ( threat, index ) => ( -
    - { isBulk ? ( -
    -
    - { ! isSm && ( -
    - -
    - ) } -
    - { threat.title } - -
    -
    - { ! isSm && !! threat.severity && ( - - ) } - selectedThreat.id === threat.id - ) } - onChange={ toggleHandlers[ threat.id ] } - /> -
    - ) : ( - <> - - - - - - ) } -
    - ) ) } - { siteCredentialsNeeded && userConnectionNeeded && ( - - ) } - { ! siteCredentialsNeeded && userConnectionNeeded && ( - - ) } - { siteCredentialsNeeded && ! userConnectionNeeded && ( - + { currentThreats.map( threat => + isBulk ? renderBulkThreat( threat ) : renderIndividualThreat( threat ) ) } + { handleUpgradeClick && ( Date: Fri, 20 Dec 2024 10:31:16 -0800 Subject: [PATCH 150/290] Update headings, improve mobile styling --- .../components/threats-modal/index.tsx | 27 ++++------ .../threats-modal/styles.module.scss | 52 ++++++++++++------- .../threats-modal/threat-fix-confirmation.tsx | 16 ++++-- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index e8d4977f71c67..b86c231b41793 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -1,8 +1,8 @@ import { type Threat } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { createContext } from 'react'; import Text from '../text'; -import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; import ThreatFixConfirmation from './threat-fix-confirmation'; @@ -82,24 +82,19 @@ export default function ThreatsModal( { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; const isBulk = currentThreats.length > 1; - const firstThreat = currentThreats[ 0 ]; + + let modalTitle = __( 'Threat details', 'jetpack-components' ); + if ( isBulk ) { + modalTitle = __( 'Fix all threats', 'jetpack-components' ); + } else if ( actionToConfirm === 'fix' ) { + modalTitle = __( 'Fix threat', 'jetpack-components' ); + } else if ( actionToConfirm === 'ignore' ) { + modalTitle = __( 'Ignore threat', 'jetpack-components' ); + } return ( - { isBulk ? ( - { 'Fix all threats' } - ) : ( - <> - { firstThreat.title } - { !! firstThreat.severity && ( - - ) } - - ) } -
    - } + title={ { modalTitle } } size="large" { ...modalProps } > diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index 411880d00fb84..892a96e9d353f 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -1,26 +1,26 @@ -.threat-details { +.threat__details { display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 3 ); // 24px + } -.section { +.individual { display: flex; flex-direction: column; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px - .section__toggle { - text-decoration: none; - - &:hover { - text-decoration: underline; - } - - &__content { - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px - align-items: center; - } + &__heading { + display: flex; + gap: calc( var( --spacing-base) * 2 ); // 16px + flex-direction: column; + } + + &__title { + display: flex; + gap: calc( var( --spacing-base) * 2 ); // 16px + flex-wrap: wrap; + align-items: center; } } @@ -32,7 +32,7 @@ justify-content: space-between; align-items: center; - &__content { + &__heading { flex: 1; display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px @@ -63,10 +63,24 @@ } } -.title { +.section { display: flex; - align-items: center; - gap: calc( var( --spacing-base ) * 1.5 ); // 12px + flex-direction: column; + gap: calc( var( --spacing-base ) * 2 ); // 16px + + .section__toggle { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + &__content { + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + align-items: center; + } + } } .filename { diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 59160613fe787..1752ea9872e12 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -49,7 +49,7 @@ const ThreatFixConfirmation = () => { const renderBulkThreat = threat => (
    -
    +
    { ! isSm && (
    @@ -71,8 +71,14 @@ const ThreatFixConfirmation = () => { ); const renderIndividualThreat = threat => ( -
    - +
    +
    +
    + { threat.title } + { !! threat.severity && } +
    + +
    @@ -80,7 +86,7 @@ const ThreatFixConfirmation = () => { ); return ( - <> +
    { isBulk && { 'Jetpack will be fixing the selected threats:' } } { currentThreats.map( threat => isBulk ? renderBulkThreat( threat ) : renderIndividualThreat( threat ) @@ -97,7 +103,7 @@ const ThreatFixConfirmation = () => { /> ) } - +
    ); }; From 91776461523960becb29cf552c061969ca9c4a20 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 10:47:16 -0800 Subject: [PATCH 151/290] Further mobile adjustments --- .../components/components/threats-modal/styles.module.scss | 4 ++-- .../components/threats-modal/threat-fix-confirmation.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index 892a96e9d353f..3ccf6067e5ae6 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -12,15 +12,15 @@ &__heading { display: flex; - gap: calc( var( --spacing-base) * 2 ); // 16px flex-direction: column; + gap: calc( var( --spacing-base) * 2 ); // 16px } &__title { display: flex; + align-items: center; gap: calc( var( --spacing-base) * 2 ); // 16px flex-wrap: wrap; - align-items: center; } } diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 1752ea9872e12..1d2dc0ff7287c 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -60,7 +60,7 @@ const ThreatFixConfirmation = () => {
    - { ! isSm && !! threat.severity && } + { !! threat.severity && } Date: Fri, 20 Dec 2024 11:37:01 -0800 Subject: [PATCH 152/290] Use computed label over title --- .../threats-modal/threat-fix-confirmation.tsx | 6 +++--- .../js-packages/scan/src/types/threats.ts | 3 +++ projects/js-packages/scan/src/utils/index.ts | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 1d2dc0ff7287c..dc17b211f0a45 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -1,4 +1,4 @@ -import { getThreatType, type Threat, THREAT_ICONS } from '@automattic/jetpack-scan'; +import { getLabel, getThreatType, type Threat, THREAT_ICONS } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useContext, useState, useCallback, useMemo } from 'react'; @@ -56,7 +56,7 @@ const ThreatFixConfirmation = () => {
    ) }
    - { threat.title } + { getLabel( threat ) }
    @@ -74,7 +74,7 @@ const ThreatFixConfirmation = () => {
    - { threat.title } + { getLabel( threat ) } { !! threat.severity && }
    diff --git a/projects/js-packages/scan/src/types/threats.ts b/projects/js-packages/scan/src/types/threats.ts index 22f6b06477163..9eb732e80a98c 100644 --- a/projects/js-packages/scan/src/types/threats.ts +++ b/projects/js-packages/scan/src/types/threats.ts @@ -75,4 +75,7 @@ export type Threat = { /** The affected extension. */ extension?: Extension; + + /** The affected version */ + version?: string; }; diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 30a96cbd132d5..1704481053581 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -156,7 +156,7 @@ export const getFixerDescription = ( threat: Threat ) => { if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ - __( 'Update %1$s to version %2$s', 'jetpack-scan' ), + __( 'Update %1$s to version %2$s.', 'jetpack-scan' ), threat.extension.name, threat.fixedIn ); @@ -187,3 +187,20 @@ export const getFixerDescription = ( threat: Threat ) => { return __( 'Jetpack will auto-fix the threat.', 'jetpack-scan' ); } }; + +export const getLabel = ( threat: Threat ) => { + if ( threat.signature === 'Vulnerable.WP.Core' ) { + // Core threat i.e. "WordPress (5.8)" + return `WordPress (${ threat.version })`; + } + + if ( threat.extension?.name && threat.extension?.version ) { + // Extension threat i.e. "Woocommerce (3.0.0)" + return `${ threat.extension.name } (${ threat.extension.version })`; + } + + if ( threat.filename ) { + // File threat i.e. "index.php" + return threat.filename.split( '/' ).pop(); + } +}; From 10f57c6ffe19fbf12fff93df597199d85dec3c9a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 12:01:06 -0800 Subject: [PATCH 153/290] Reapply original title setup with now shortened content --- .../components/threats-modal/index.tsx | 24 ++++++++++--------- .../threats-modal/styles.module.scss | 21 ++++++---------- .../threats-modal/threat-fix-confirmation.tsx | 8 +------ 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index b86c231b41793..e594be476badf 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -1,8 +1,9 @@ -import { type Threat } from '@automattic/jetpack-scan'; +import { getLabel, type Threat } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { createContext } from 'react'; import Text from '../text'; +import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; import ThreatFixConfirmation from './threat-fix-confirmation'; @@ -82,19 +83,20 @@ export default function ThreatsModal( { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; const isBulk = currentThreats.length > 1; - - let modalTitle = __( 'Threat details', 'jetpack-components' ); - if ( isBulk ) { - modalTitle = __( 'Fix all threats', 'jetpack-components' ); - } else if ( actionToConfirm === 'fix' ) { - modalTitle = __( 'Fix threat', 'jetpack-components' ); - } else if ( actionToConfirm === 'ignore' ) { - modalTitle = __( 'Ignore threat', 'jetpack-components' ); - } + const firstThreat = currentThreats[ 0 ]; return ( { modalTitle } } + title={ + isBulk ? ( + { __( 'Fix all threats', 'jetpack-components' ) } + ) : ( +
    + { getLabel( firstThreat ) } + { !! firstThreat.severity && } +
    + ) + } size="large" { ...modalProps } > diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index 3ccf6067e5ae6..16ff30b2e7221 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -1,27 +1,20 @@ +.threat__title { + display: flex; + align-items: center; + gap: calc( var( --spacing-base) * 2 ); // 16px + flex-wrap: wrap; +} + .threat__details { display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 3 ); // 24px - } .individual { display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 3 ); // 24px - - &__heading { - display: flex; - flex-direction: column; - gap: calc( var( --spacing-base) * 2 ); // 16px - } - - &__title { - display: flex; - align-items: center; - gap: calc( var( --spacing-base) * 2 ); // 16px - flex-wrap: wrap; - } } .bulk { diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index dc17b211f0a45..2db7f3a64a3f0 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -72,13 +72,7 @@ const ThreatFixConfirmation = () => { const renderIndividualThreat = threat => (
    -
    -
    - { getLabel( threat ) } - { !! threat.severity && } -
    - -
    + From 3baeb4f0841ea01ea28aef591541ea2ea7c932d0 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 13:58:39 -0800 Subject: [PATCH 154/290] Improve styles --- .../threats-modal/styles.module.scss | 28 +++++++++++++------ .../threats-modal/threat-actions.tsx | 2 +- .../threats-modal/threat-fix-confirmation.tsx | 10 +++++-- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index 16ff30b2e7221..b85c0c4e9b083 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -18,12 +18,22 @@ } .bulk { - border: 1px solid var( --jp-gray-5 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px; + border-bottom: 1px solid #f0f0f0; + padding: calc( var( --spacing-base ) * 2 ); // 16px; gap: calc( var( --spacing-base ) * 3 ); // 24px; display: flex; justify-content: space-between; align-items: center; + height: 112px; + cursor: pointer; + + &:first-of-type { + border-top: 1px solid #f0f0f0; + } + + &:hover { + background-color: #f8f8f8 + } &__heading { flex: 1; @@ -84,14 +94,14 @@ .modal-footer { padding-top: calc( var( --spacing-base ) * 3 ); // 24px - border-top: 1px solid var( --jp-gray-5 ); + border-top: 1px solid #f0f0f0; +} - .threat-actions { - display: flex; - justify-content: flex-end; - flex-wrap: wrap; - gap: calc( var( --spacing-base ) * 2 ); // 16px; - } +.threat-actions { + display: flex; + justify-content: flex-end; + flex-wrap: wrap; + gap: calc( var( --spacing-base ) * 2 ); // 16px; } .fixer-notice { diff --git a/projects/js-packages/components/components/threats-modal/threat-actions.tsx b/projects/js-packages/components/components/threats-modal/threat-actions.tsx index 2c833fc5f3d79..77aebd9290180 100644 --- a/projects/js-packages/components/components/threats-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-actions.tsx @@ -100,7 +100,7 @@ const ThreatActions = ( { selectedThreats }: { selectedThreats: Threat[] } ): JS ); return ( -
    +
    { ! isBulk && }
    { isBulk ? renderBulkActions() : renderIndividualActions() } diff --git a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx index 2db7f3a64a3f0..155f2a650262e 100644 --- a/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threats-modal/threat-fix-confirmation.tsx @@ -81,9 +81,13 @@ const ThreatFixConfirmation = () => { return (
    - { isBulk && { 'Jetpack will be fixing the selected threats:' } } - { currentThreats.map( threat => - isBulk ? renderBulkThreat( threat ) : renderIndividualThreat( threat ) + { isBulk ? ( + <> + { 'Jetpack will be fixing the selected threats:' } +
    { currentThreats.map( threat => renderBulkThreat( threat ) ) }
    + + ) : ( + currentThreats.map( threat => renderIndividualThreat( threat ) ) ) } { handleUpgradeClick && ( From 5601445a6bc7dc0d789820fe79d0e05fa3edda3e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 20 Dec 2024 14:49:06 -0800 Subject: [PATCH 155/290] Fix console errors --- .../components/threats-modal/index.tsx | 20 +++++++++++-------- .../threats-modal/styles.module.scss | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/projects/js-packages/components/components/threats-modal/index.tsx b/projects/js-packages/components/components/threats-modal/index.tsx index e594be476badf..69b1eb449b03c 100644 --- a/projects/js-packages/components/components/threats-modal/index.tsx +++ b/projects/js-packages/components/components/threats-modal/index.tsx @@ -88,14 +88,18 @@ export default function ThreatsModal( { return ( { __( 'Fix all threats', 'jetpack-components' ) } - ) : ( -
    - { getLabel( firstThreat ) } - { !! firstThreat.severity && } -
    - ) + + { isBulk ? ( + __( 'Fix all threats', 'jetpack-components' ) + ) : ( + <> + { getLabel( firstThreat ) } + { !! firstThreat.severity && ( + + ) } + + ) } + } size="large" { ...modalProps } diff --git a/projects/js-packages/components/components/threats-modal/styles.module.scss b/projects/js-packages/components/components/threats-modal/styles.module.scss index b85c0c4e9b083..31e9f442132b5 100644 --- a/projects/js-packages/components/components/threats-modal/styles.module.scss +++ b/projects/js-packages/components/components/threats-modal/styles.module.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; gap: calc( var( --spacing-base) * 2 ); // 16px - flex-wrap: wrap; + font-size: 20px; } .threat__details { @@ -24,7 +24,7 @@ display: flex; justify-content: space-between; align-items: center; - height: 112px; + min-height: 120px; cursor: pointer; &:first-of-type { From 609a47eae56aa6300e4af8faec495eb1a3761def Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 14 Nov 2024 10:42:12 -0700 Subject: [PATCH 156/290] Init project branch From 43b956623f99f87620027017f78db40f6735101f Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:03:36 -0800 Subject: [PATCH 157/290] Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller --- .../changelog/add-protect-header-buttons | 4 +++ .../src/js/components/admin-page/index.jsx | 27 +++++++++++++++++-- .../components/admin-page/styles.module.scss | 10 +++++++ .../src/js/components/scan-button/index.jsx | 4 +++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/protect/changelog/add-protect-header-buttons diff --git a/projects/plugins/protect/changelog/add-protect-header-buttons b/projects/plugins/protect/changelog/add-protect-header-buttons new file mode 100644 index 0000000000000..24c40f542d7ee --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-header-buttons @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Adds Go to Cloud and Scan now buttons to the primary header diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4579831b5f0a5..4e93ae443aa72 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -1,16 +1,20 @@ import { AdminPage as JetpackAdminPage, + Button, Container, + getRedirectUrl, JetpackProtectLogo, } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import useNotices from '../../hooks/use-notices'; +import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; +import ScanButton from '../scan-button'; import Tabs, { Tab } from '../tabs'; import styles from './styles.module.scss'; @@ -24,6 +28,8 @@ const AdminPage = ( { children } ) => { current: { threats: numThreats }, }, } = useProtectData(); + const location = useLocation(); + const { hasPlan } = usePlan(); // Redirect to the setup page if the site is not registered. useEffect( () => { @@ -36,10 +42,27 @@ const AdminPage = ( { children } ) => { return null; } + const viewingScanPage = location.pathname.includes( '/scan' ); + + const { siteSuffix, blogID } = window.jetpackProtectInitialState || {}; + const goToCloudUrl = getRedirectUrl( 'jetpack-scan-dash', { site: blogID ?? siteSuffix } ); + return ( } + header={ +
    + + { hasPlan && viewingScanPage && ( +
    + + +
    + ) } +
    + } > { notice && } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index e70d2cdb076c7..adf7dc594b907 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From 0f9d3550c15483d10d7587776c3f8d6e15581394 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 158/290] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 9c8f30b7b8067..4aa517f5f120b 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import AdminSectionHero from '../../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { oldestFirstDetected ? ( + + { sprintf( + /* translators: %s: Oldest first detected date */ + __( '%s - Today', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', oldestFirstDetected, false ) + ) } + + ) : ( + __( 'Most recent results', 'jetpack-protect' ) + ) } + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } + { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..1c5cc6cac49b9 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; +import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useFixers from '../../hooks/use-fixers'; +import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // List of fixable threats that do not have a fix in progress + const fixableList = useMemo( () => { + return list.filter( threat => { + const threatId = parseInt( threat.id ); + return ( + threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) + ); + } ); + }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
    - -
    } /> diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From d7ec48ac2de74454d8d93e068e2a9d3a05a4a714 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 159/290] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
    - diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adf7dc594b907..adc0cee561ba5 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From 3195cb38964c3bd1ddd92391b278145ed53b4f6a Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 29 Nov 2024 21:00:37 -0700 Subject: [PATCH 160/290] Protect: add ShieldIcon component --- .../components/admin-section-hero/index.tsx | 21 ++- .../stories/index.stories.jsx | 4 +- .../src/js/components/shield-icon/index.tsx | 165 ++++++++++++++++++ .../shield-icon/stories/index.stories.tsx | 50 ++++++ 4 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ed83bebc8638..758c8c21e0193 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,9 +1,6 @@ -import { - AdminSectionHero as JetpackAdminSectionHero, - H3, - getIconBySlug, -} from '@automattic/jetpack-components'; +import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; +import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -15,7 +12,7 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean } >; + Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -44,17 +41,23 @@ const AdminSectionHero: AdminSectionHeroComponent = ( { AdminSectionHero.Heading = ( { children, + variant = 'default', showIcon = false, }: { children: React.ReactNode; + variant?: 'default' | 'success' | 'error'; showIcon?: boolean; } ) => { - const Icon = getIconBySlug( 'protect' ); - return (

    { children } - { showIcon && } + { showIcon && ( + + ) }

    ); }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index 7d5b4f8066c93..ca2dfda7fc98e 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -12,7 +12,9 @@ Default.args = { main: ( <> - { 'No threats found' } + + { 'No threats found' } + { 'Most recent results' } diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..3bf7f479f0051 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/index.tsx @@ -0,0 +1,165 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.variant - Icon variant. + * @param {string} props.fill - Icon fill color. + * @param {string} props.className - Additional class names. + * @param {number} props.height - Icon height. + * @return {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ShieldIcon( { + variant = 'default', + height = 32, + className, + fill, +}: { + variant: + | 'default' + | 'success' + | 'error' + | 'default-outline' + | 'success-outline' + | 'error-outline'; + className?: string; + height?: number; + fill?: string; +} ): JSX.Element { + if ( 'error-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'error' === variant ) { + return ( + + + + + ); + } + + if ( 'success-outline' === variant ) { + return ( + + + + + ); + } + + if ( 'success' === variant ) { + return ( + + + + + ); + } + + if ( 'default-outline' === variant ) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..d10365f4b0834 --- /dev/null +++ b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ShieldIcon from '../index'; + +export default { + title: 'Plugins/Protect/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
    + +
    + ), + ], + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ + 'default', + 'success', + 'error', + 'default-outline', + 'success-outline', + 'error-outline', + ], + }, + fill: { + control: 'color', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'default', +}; + +export const SuccessVariant = args => ; +SuccessVariant.args = { + variant: 'success', +}; + +export const ErrorVariant = args => ; +ErrorVariant.args = { + variant: 'error', +}; From 1b4eca4723d485494c1ced4cfd4f5100a2e23ec4 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 4 Dec 2024 20:26:54 -0700 Subject: [PATCH 161/290] Protect: Add ShieldIcon Component (#40402) --- .../components/changelog/add-shield-icon | 4 + .../components/shield-icon/index.tsx | 79 +++++++++ .../shield-icon/stories/index.stories.tsx | 54 ++++++ projects/js-packages/components/index.ts | 1 + .../protect/changelog/refactor-alert-icon | 5 + .../components/admin-section-hero/index.tsx | 20 ++- .../admin-section-hero/styles.module.scss | 4 +- .../src/js/components/alert-icon/index.jsx | 74 -------- .../alert-icon/stories/index.stories.jsx | 17 -- .../components/alert-icon/styles.module.scss | 11 -- .../src/js/components/shield-icon/index.tsx | 165 ------------------ .../shield-icon/stories/index.stories.tsx | 50 ------ .../history/history-admin-section-hero.tsx | 2 +- .../routes/scan/scan-admin-section-hero.tsx | 2 +- 14 files changed, 162 insertions(+), 326 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-shield-icon create mode 100644 projects/js-packages/components/components/shield-icon/index.tsx create mode 100644 projects/js-packages/components/components/shield-icon/stories/index.stories.tsx create mode 100644 projects/plugins/protect/changelog/refactor-alert-icon delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx diff --git a/projects/js-packages/components/changelog/add-shield-icon b/projects/js-packages/components/changelog/add-shield-icon new file mode 100644 index 0000000000000..5c6cc27eeb809 --- /dev/null +++ b/projects/js-packages/components/changelog/add-shield-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add ShieldIcon component diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx new file mode 100644 index 0000000000000..fee9f4d70c463 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +const COLORS = { + error: '#D63638', + warning: '#F0B849', + success: '#069E08', + default: '#1d2327', +}; + +/** + * Protect Shield SVG Icon + * + * @param {object} props - Component props. + * @param {string} props.className - Additional class names. + * @param {string} props.contrast - Icon contrast color. Overrides variant. + * @param {string} props.fill - Icon fill color (default, success, warning, error, or a custom color code string). Overrides variant. + * @param {number} props.height - Icon height (px). Width is calculated based on height. + * @param {string} props.icon - Icon variant (success, error). Overrides variant. + * @param {boolean} props.outline - When enabled, the icon will use an outline style. + * @param {string} props.variant - Icon variant (default, success, error). + * + * @return {React.ReactElement} Protect Shield SVG Icon + */ +export default function ShieldIcon( { + className, + contrast = '#fff', + fill, + height = 32, + icon, + outline = false, + variant = 'default', +}: { + className?: string; + contrast?: string; + fill?: 'default' | 'success' | 'warning' | 'error' | string; + height?: number; + icon?: 'success' | 'error'; + outline?: boolean; + variant: 'default' | 'success' | 'warning' | 'error'; +} ): JSX.Element { + const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; + const iconFill = outline ? shieldFill : contrast; + const iconVariant = icon || variant; + + return ( + + + { 'success' === iconVariant && ( + + ) } + { [ 'warning', 'error' ].includes( iconVariant ) && ( + + ) } + + ); +} diff --git a/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx new file mode 100644 index 0000000000000..b5a16d4da4075 --- /dev/null +++ b/projects/js-packages/components/components/shield-icon/stories/index.stories.tsx @@ -0,0 +1,54 @@ +import ShieldIcon from '../index'; + +export default { + title: 'JS Packages/Components/Sheild Icon', + component: ShieldIcon, + parameters: { + layout: 'centered', + }, + argTypes: { + variant: { + control: { + type: 'select', + }, + options: [ 'default', 'success', 'warning', 'error' ], + }, + icon: { + control: { + type: 'select', + }, + options: [ 'success', 'error' ], + }, + fill: { + control: 'color', + }, + outline: { + control: 'boolean', + }, + }, +}; + +export const Default = args => ; +Default.args = { + variant: 'success', + outline: false, +}; + +export const Variants = () => { + return ( +
    +
    + + + + +
    +
    + + + + +
    +
    + ); +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index eb90df97ad5fe..4b0f3612012e7 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -47,6 +47,7 @@ export { default as ThemeProvider } from './components/theme-provider'; export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; +export { default as ShieldIcon } from './components/shield-icon'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/plugins/protect/changelog/refactor-alert-icon b/projects/plugins/protect/changelog/refactor-alert-icon new file mode 100644 index 0000000000000..46b4c247b1b9f --- /dev/null +++ b/projects/plugins/protect/changelog/refactor-alert-icon @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Refactored icon component code. + + diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 758c8c21e0193..5ccf607698084 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -1,6 +1,9 @@ -import { AdminSectionHero as JetpackAdminSectionHero, H3 } from '@automattic/jetpack-components'; +import { + AdminSectionHero as JetpackAdminSectionHero, + H3, + ShieldIcon, +} from '@automattic/jetpack-components'; import SeventyFiveLayout from '../seventy-five-layout'; -import ShieldIcon from '../shield-icon'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; @@ -12,7 +15,12 @@ interface AdminSectionHeroProps { } interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { children: React.ReactNode; showIcon?: boolean; variant?: string } >; + Heading: React.FC< { + children: React.ReactNode; + showIcon?: boolean; + variant?: 'default' | 'success' | 'error'; + outline?: boolean; + } >; Subheading: React.FC< { children: React.ReactNode } >; } @@ -53,8 +61,10 @@ AdminSectionHero.Heading = ( { { children } { showIcon && ( ) } diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index 5881bcd910045..a414aa9216f5c 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -13,7 +13,7 @@ } .heading-icon { - margin-left: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } @@ -23,4 +23,4 @@ .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px -} \ No newline at end of file +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
    - - - - - - - - - - - - - - - - - - - -
    - ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx deleted file mode 100644 index 47b2ee32d4b51..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import AlertIcon from '../index.jsx'; - -export default { - title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, -}; - -const FooterTemplate = args => ; -export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/index.tsx b/projects/plugins/protect/src/js/components/shield-icon/index.tsx deleted file mode 100644 index 3bf7f479f0051..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @param {object} props - Component props. - * @param {string} props.variant - Icon variant. - * @param {string} props.fill - Icon fill color. - * @param {string} props.className - Additional class names. - * @param {number} props.height - Icon height. - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ShieldIcon( { - variant = 'default', - height = 32, - className, - fill, -}: { - variant: - | 'default' - | 'success' - | 'error' - | 'default-outline' - | 'success-outline' - | 'error-outline'; - className?: string; - height?: number; - fill?: string; -} ): JSX.Element { - if ( 'error-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'error' === variant ) { - return ( - - - - - ); - } - - if ( 'success-outline' === variant ) { - return ( - - - - - ); - } - - if ( 'success' === variant ) { - return ( - - - - - ); - } - - if ( 'default-outline' === variant ) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx b/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx deleted file mode 100644 index d10365f4b0834..0000000000000 --- a/projects/plugins/protect/src/js/components/shield-icon/stories/index.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ShieldIcon from '../index'; - -export default { - title: 'Plugins/Protect/Sheild Icon', - component: ShieldIcon, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], - argTypes: { - variant: { - control: { - type: 'select', - }, - options: [ - 'default', - 'success', - 'error', - 'default-outline', - 'success-outline', - 'error-outline', - ], - }, - fill: { - control: 'color', - }, - }, -}; - -export const Default = args => ; -Default.args = { - variant: 'default', -}; - -export const SuccessVariant = args => ; -SuccessVariant.args = { - variant: 'success', -}; - -export const ErrorVariant = args => ; -ErrorVariant.args = { - variant: 'error', -}; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx index 4aa517f5f120b..141c51cde284a 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx @@ -60,7 +60,7 @@ const HistoryAdminSectionHero: React.FC = () => { __( 'Most recent results', 'jetpack-protect' ) ) } - + { numAllThreats > 0 ? sprintf( /* translators: %s: Total number of threats */ diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 1c5cc6cac49b9..9e1b9c102a037 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -95,7 +95,7 @@ const ScanAdminSectionHero: React.FC = () => { anchor={ dailyScansPopoverAnchor } /> ) } - + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ From 3d878c74bfe5558c5b3c61c1c6619da877e88a6e Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 5 Dec 2024 10:50:27 -0700 Subject: [PATCH 162/290] Protect: Integrate ThreatsDataViews Component (#40076) --- pnpm-lock.yaml | 3 + .../add-threat-subtitle-and-icon-utils | 4 + projects/js-packages/scan/src/utils/index.ts | 32 +- .../protect/changelog/add-threats-data-views | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 30 +- projects/plugins/protect/src/js/api.ts | 3 +- .../src/js/components/admin-page/index.jsx | 12 +- .../components/admin-page/styles.module.scss | 11 + .../error-admin-section-hero/index.tsx | 4 - .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 14 +- .../src/js/components/navigation/badge.jsx | 101 ------ .../src/js/components/navigation/group.jsx | 51 --- .../src/js/components/navigation/index.jsx | 73 ----- .../src/js/components/navigation/item.jsx | 85 ----- .../src/js/components/navigation/label.jsx | 24 -- .../components/navigation/styles.module.scss | 142 --------- .../navigation/use-menu-navigation.js | 92 ------ .../js/components/paid-accordion/index.jsx | 192 ----------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 202 ------------ .../src/js/components/pricing-table/index.jsx | 4 +- .../components/protect-check-icon/index.tsx | 25 -- .../js/components/scan-navigation/index.jsx | 44 --- .../js/components/threat-fix-header/index.jsx | 7 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 -------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 -------- .../js/components/threats-list/pagination.jsx | 142 --------- .../js/components/threats-list/paid-list.jsx | 253 --------------- .../threats-list/styles.module.scss | 129 -------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/hooks/use-protect-data/index.ts | 173 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../protect/src/js/routes/firewall/index.jsx | 3 +- .../history/history-admin-section-hero.tsx | 84 ----- .../src/js/routes/scan/history/index.jsx | 301 ------------------ .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 37 --- .../protect/src/js/routes/scan/index.jsx | 79 +++-- .../src/js/routes/scan/onboarding-steps.jsx | 61 ++-- .../routes/scan/scan-admin-section-hero.tsx | 138 ++++---- .../src/js/routes/scan/scan-footer.jsx | 143 --------- .../js/routes/scan/scan-results-data-view.tsx | 56 ++++ .../scan/scanning-admin-section-hero.tsx | 25 +- .../src/js/routes/scan/styles.module.scss | 20 +- projects/plugins/protect/webpack.config.js | 18 ++ 53 files changed, 363 insertions(+), 3668 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils create mode 100644 projects/plugins/protect/changelog/add-threats-data-views delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/badge.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/group.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/item.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/label.jsx delete mode 100644 projects/plugins/protect/src/js/components/navigation/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/scan-navigation/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/routes/scan/scan-footer.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 889049053da1f..a9022db11c141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4255,6 +4255,9 @@ importers: specifier: 6.2.2 version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@automattic/babel-plugin-replace-textdomain': + specifier: workspace:* + version: link:../../js-packages/babel-plugin-replace-textdomain '@automattic/jetpack-webpack-config': specifier: workspace:* version: link:../../js-packages/webpack-config diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils new file mode 100644 index 0000000000000..ad8fa81458278 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add utilities for generating threat subtitle and icons diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 945cd0ecb7fa8..9e2e75bcd4d91 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => { return null; }; +export const getThreatIcon = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return 'wordpress-alt'; + case 'plugin': + return 'plugins'; + case 'theme': + return 'appearance'; + case 'file': + return 'media-code'; + default: + return 'shield-alt'; + } +}; + +export const getThreatSubtitle = ( threat: Threat ) => { + switch ( getThreatType( threat ) ) { + case 'core': + return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); + case 'plugin': + return __( 'Vulnerable Plugin', 'jetpack-scan' ); + case 'theme': + return __( 'Vulnerable Theme', 'jetpack-scan' ); + case 'file': + return __( 'File Threat', 'jetpack-scan' ); + default: + return __( 'Threat', 'jetpack-scan' ); + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); @@ -123,7 +153,7 @@ export const getFixerDescription = ( threat: Threat ) => { } break; case 'update': - if ( threat.fixedIn && threat.extension.name ) { + if ( threat.fixedIn && threat.extension?.name ) { return sprintf( /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ __( 'Update %1$s to version %2$s', 'jetpack-scan' ), diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views new file mode 100644 index 0000000000000..e15bd6a461a71 --- /dev/null +++ b/projects/plugins/protect/changelog/add-threats-data-views @@ -0,0 +1,5 @@ +Significance: minor +Type: changed + +Added DataViews component for viewing scan results. + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index e408077e068bf..1751df7e06f5a 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -49,6 +49,7 @@ "react-router-dom": "6.2.2" }, "devDependencies": { + "@automattic/babel-plugin-replace-textdomain": "workspace:*", "@automattic/jetpack-webpack-config": "workspace:*", "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index bd034c375caf9..8ea1dec7156e7 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -207,43 +207,19 @@ public static function fetch_from_api() { * Normalize API Data * Formats the payload from the Scan API into an instance of History_Model. * - * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility. - * * @param object $scan_data The data returned by the scan API. * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..97d11fd5c0f2b 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,6 +1,7 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { WafStatus } from './types/waf'; const API = { getWaf: (): Promise< WafStatus > => diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 2d023560517f3..68f9359a9bd81 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useNotices from '../../hooks/use-notices'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import Notice from '../notice'; import ScanButton from '../scan-button'; @@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => { const { isRegistered } = useConnection(); const { isSeen: wafSeen } = useWafData(); const navigate = useNavigate(); - const { - counts: { - current: { threats: numThreats }, - }, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); const location = useLocation(); const { hasPlan } = usePlan(); @@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => { link="/scan" label={ - { numThreats > 0 + { status.threats.length > 0 ? sprintf( // translators: %d is the number of threats found. __( 'Scan (%d)', 'jetpack-protect' ), - numThreats + status.threats.length ) : __( 'Scan', 'jetpack-protect' ) } diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss index adc0cee561ba5..da2e9510cd7d9 100644 --- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss @@ -5,10 +5,21 @@ .header { display: flex; justify-content: space-between; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px + align-items: center; + + @media ( min-width: 600px ) { + flex-direction: row; + } &__scan_buttons { display: flex; gap: calc( var( --spacing-base ) * 3 ); // 24px + + @media ( min-width: 600px ) { + flex-direction: row; + } } } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 5214531dcf362..1a9bc87387fa9 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; -import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; interface ErrorAdminSectionHeroProps { @@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { { displayErrorMessage } -
    - -
    } preserveSecondaryOnMobile={ false } diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
    - +
    diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx index 7e8113b6f38ab..0788eb8bd7a41 100644 --- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx @@ -1,16 +1,18 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation'; import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx deleted file mode 100644 index 93ebecf7235ef..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/badge.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, check, info } from '@wordpress/icons'; -import PropTypes from 'prop-types'; -import React, { useState, useCallback, useMemo } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import styles from './styles.module.scss'; - -/** - * Gets the Badge element - * - * @param {number} count - The number of threats found for this item. - * @param {boolean} checked - Whether this item was checked for threats yet. - * @return {object} The badge element - */ -const getBadgeElement = ( count, checked ) => { - if ( ! checked ) { - return { - popoverText: __( - 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', - 'jetpack-protect' - ), - badgeElement: ( - - ), - }; - } - - if ( count === 0 ) { - return { - popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ), - badgeElement: ( - - ), - }; - } - - return { - popoverText: null, - badgeElement: ( - - { count } - - ), - }; -}; - -const ItemBadge = ( { count, checked } ) => { - const { data: status } = useScanStatusQuery(); - - const { popoverText, badgeElement } = getBadgeElement( count, checked ); - const [ showPopover, setShowPopover ] = useState( false ); - - const inProgress = useMemo( () => isScanInProgress( status ), [ status ] ); - - const handleEnter = useCallback( () => { - if ( inProgress ) { - return; - } - - setShowPopover( true ); - }, [ inProgress ] ); - - const handleOut = useCallback( () => { - setShowPopover( false ); - }, [] ); - - return ( -
    - { ! inProgress ? badgeElement : } - { showPopover && ( - - - { popoverText } - - - ) } -
    - ); -}; - -ItemBadge.propTypes = { - /* The number of threats found for this item */ - count: PropTypes.number, - /* Whether this item was checked for threats yet */ - checked: PropTypes.bool, -}; - -export default ItemBadge; diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx deleted file mode 100644 index 9352ae5c63d67..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/group.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useState, useCallback, useContext } from 'react'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const MAX_ITEMS = 8; - -const NavigationGroup = ( { icon, label, children } ) => { - const [ collapsed, setCollapsed ] = useState( true ); - const { mode } = useContext( NavigationContext ); - const needsTruncate = - Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list'; - const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children; - const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0; - - const handleCollapsedToggle = useCallback( () => { - setCollapsed( current => ! current ); - }, [] ); - - return ( -
  • - - { label } - -
    -
      { content }
    - { needsTruncate && ( -
    - -
    - ) } -
    -
  • - ); -}; - -export default NavigationGroup; diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx deleted file mode 100644 index bd30dfbdad964..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import React, { useState, useRef, useCallback } from 'react'; -import NavigationGroup from './group'; -import NavigationItem from './item'; -import styles from './styles.module.scss'; -import useMenuNavigation, { NavigationContext } from './use-menu-navigation'; - -const NavigationList = ( { children } ) => ( -
      - { children } -
    -); - -const NavigationDropdown = ( { children, data } ) => { - const ref = useRef( undefined ); - const [ listOpen, setListOpen ] = useState( false ); - const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? { - label: __( 'See all results', 'jetpack-protect' ), - }; - const { label, icon } = item; - - const handleOpen = useCallback( () => { - setListOpen( open => ! open ); - }, [] ); - - return ( - - ); -}; - -const getNavigationComponent = mode => { - switch ( mode ) { - case 'list': - return NavigationList; - case 'dropdown': - return NavigationDropdown; - default: - return NavigationList; - } -}; - -const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => { - const data = useMenuNavigation( { selected, onSelect } ); - const Component = getNavigationComponent( mode ); - - return ( - - { children } - - ); -}; - -export default Navigation; -export { NavigationItem, NavigationGroup }; diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx deleted file mode 100644 index d902625c3997d..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/item.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import clsx from 'clsx'; -import React, { useContext, useEffect, useCallback } from 'react'; -import ItemBadge from './badge'; -import ItemLabel from './label'; -import styles from './styles.module.scss'; -import { NavigationContext } from './use-menu-navigation'; - -const NavigationItem = ( { - id, - label, - icon, - badge, - disabled, - onClick, - onKeyDown, - onFocus, - checked, -} ) => { - const context = useContext( NavigationContext ); - - const selected = context?.selectedItem === id; - const registerItem = context?.registerItem; - const registerRef = context?.registerRef; - const handleClickItem = context?.handleClickItem; - const handleKeyDownItem = context?.handleKeyDownItem; - const handleFocusItem = context?.handleFocusItem; - - const wrapperClassName = clsx( styles[ 'navigation-item' ], { - [ styles.clickable ]: ! disabled, - [ styles.selected ]: selected, - } ); - - const handleClick = useCallback( - evt => { - onClick?.( evt ); - handleClickItem?.( id ); - }, - [ handleClickItem, id, onClick ] - ); - - const handleKeyDown = useCallback( - evt => { - onKeyDown?.( evt ); - handleKeyDownItem?.( evt ); - }, - [ handleKeyDownItem, onKeyDown ] - ); - - const handleRef = useCallback( - ref => { - registerRef( ref, id ); - }, - [ registerRef, id ] - ); - - const handleFocus = useCallback( - evt => { - onFocus?.( evt ); - handleFocusItem?.( id ); - }, - [ handleFocusItem, id, onFocus ] - ); - - useEffect( () => { - registerItem( { id, disabled, label, icon } ); - // eslint-disable-next-line - }, [] ); - - return ( -
  • - { label } - -
  • - ); -}; - -export default NavigationItem; diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx deleted file mode 100644 index 8f075caae020a..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/label.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon } from '@wordpress/icons'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './styles.module.scss'; - -const ItemLabel = ( { icon, children, className } ) => { - return ( - - { icon && } - { children } - - ); -}; - -ItemLabel.propTypes = { - /* An icon that will be rendered before text */ - icon: PropTypes.node, - /* Label text that will be rendered */ - children: PropTypes.node.isRequired, -}; - -export default ItemLabel; diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss deleted file mode 100644 index df9e3ef1f8a25..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss +++ /dev/null @@ -1,142 +0,0 @@ -.navigation { - background-color: var( --jp-white ); - box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); - border-radius: var( --jp-border-radius ); - margin: 0; -} - -.navigation-item { - display: flex; - padding: calc( var( --spacing-base ) * 2 ); // 16px; - align-items: center; - justify-content: space-between; - margin: 0; - text-align: left; - - // Clickable State - &.clickable { - cursor: pointer; - outline-color: var( --jp-black ); - - // Focus/Hover State - &:hover:not(.selected), - &:focus:not(.selected) { - background-color: var( --jp-gray-0 ); - } - } - - // Selected State - &.selected { - background-color: var( --jp-black ); - - & .navigation-item-label { - color: var( --jp-white ) - } - - & .navigation-item-icon { - fill: var( --jp-white ); - } - - & .navigation-item-badge { - border: 1px solid var( --jp-red ); - background-color: var( --jp-red ); - color: var( --jp-white ); - } - } - - // CHILDRENS - - // .navigation-item-label - &-label { - display: flex; - align-items: center; - padding-right: var( --spacing-base ); // 8px - overflow-x: hidden; - } - - // .navigation-item-label-content - &-label-text { - display: block; - overflow-x: hidden; - text-overflow: ellipsis; - } - - // .navigation-item-icon - &-icon { - margin-right: calc( var( --spacing-base ) * 2); // 16px - } - - // .navigation-item-badge - &-badge { - border: 1px solid var( --jp-red-60 ); - color: var( --jp-red-60 ); - border-radius: 50%; - padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px - min-width: 30px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - &-check-badge { - fill: var( --jp-green-50 ); - } - - &-info-badge { - fill: var( --jp-gray-20 ); - } -} - -.navigation-group { - --icon-size: 28px; - --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px - --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px - - list-style: none; - - &-label { - padding: calc( var( --spacing-base ) * 2 ); // 16px - } - - &-content { - padding: 0; - } - - &-list { - margin-left: var( --left-spacing ) ; - } - - &-truncate { - padding: calc( var( --spacing-base ) * 2 ); // 16px - display: flex; - justify-content: flex-start; - } -} - -.popover-text { - width: 250px; - padding: calc( var( --spacing-base ) * 2 ); // 16px -} - -.navigation-dropdown { - &-button { - display: flex; - border: 1px solid var( --jp-gray-10 ); - border-radius: var( --jp-border-radius ); - padding: calc( var( --spacing-base ) * 2 ); // 16px - background-color: var( --jp-white ); - justify-content: space-between; - align-items: center; - width: 100%; - } - - &-label { - display: flex; - justify-content: flex-start; - } - - &-icon { - margin-right: var( --spacing-base ); // 8px - } -} diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js deleted file mode 100644 index 2972dac06b572..0000000000000 --- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -export const NavigationContext = React.createContext(); - -const useMenuNavigation = ( { selected, onSelect } ) => { - const [ items, setItems ] = useState( [] ); - const [ refs, setRef ] = useState( [] ); - const [ focusedItem, setFocusedItem ] = useState(); - - const handleClickItem = id => { - onSelect( id ); - }; - - const handleFocusItem = id => { - setFocusedItem( id ); - }; - - const getPrevItem = ( current, last ) => { - const startMinusOne = current - 1; - const prevIndex = startMinusOne < 0 ? last : startMinusOne; - const prevItem = items[ prevIndex ]; - return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem; - }; - - const getNextItem = ( current, last ) => { - const startPlusOne = current + 1; - const nextIndex = startPlusOne > last ? 0 : startPlusOne; - const nextItem = items[ nextIndex ]; - return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem; - }; - - const handleKeyDownItem = input => { - const code = input?.code; - const current = items.findIndex( item => item?.id === selected ); - const lastIndex = items.length - 1; - - let nextId; - - if ( code === 'ArrowUp' ) { - const prevItem = getPrevItem( current, lastIndex ); - nextId = prevItem?.id; - } else if ( code === 'ArrowDown' ) { - const nextItem = getNextItem( current, lastIndex ); - nextId = nextItem?.id; - } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) { - nextId = focusedItem; - } - - if ( nextId ) { - const element = refs[ nextId ]; - element?.focus(); - onSelect( nextId ); - } - }; - - const registerRef = ( ref, id ) => { - setRef( allRefs => { - if ( ! allRefs[ id ] && ref ) { - return { ...allRefs, [ id ]: ref }; - } - return allRefs; - } ); - }; - - const registerItem = data => { - setItems( allItems => { - const newItems = [ ...allItems ]; - const id = data?.id; - const currentIdx = newItems.findIndex( item => item?.id === id ); - - if ( currentIdx >= 0 ) { - newItems[ currentIdx ] = data; - } else { - newItems.push( data ); - } - - return newItems; - } ); - }; - - return { - selectedItem: selected, - handleClickItem, - handleKeyDownItem, - handleFocusItem, - registerRef, - registerItem, - items, - }; -}; - -export default useMenuNavigation; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index c733ff1f0a08c..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - IconTooltip, - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
    - { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
    -); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - - - { createInterpolateElement( - __( - 'The fixer is taking longer than expected. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - - - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
    - -
    - { children } -
    -
    - ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
    { children }
    -
    - ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
    - -
    - ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 8304942b206d4..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,202 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -.icon-tooltip { - max-height: 20px; - margin-left: calc( var( --spacing-base ) / 2 ); // 4px - - &__icon { - color: var( --jp-red ); - } - - &__content { - color: var( --jp-gray-70 ); - font-weight: 400; - line-height: 24px; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx deleted file mode 100644 index d1100d8ce6d5e..0000000000000 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { type JSX } from 'react'; - -/** - * Protect Shield and Checkmark SVG Icon - * - * @return {JSX.Element} Protect Shield and Checkmark SVG Icon - */ -export default function ProtectCheck(): JSX.Element { - return ( - - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx deleted file mode 100644 index e626b6af066c7..0000000000000 --- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import usePlan from '../../hooks/use-plan'; -import ButtonGroup from '../button-group'; - -/** - * Navigation for scan sections. - * - * @return {React.Element} The React Component. - */ -export default function ScanNavigation() { - const navigate = useNavigate(); - const location = useLocation(); - const { hasPlan } = usePlan(); - - const viewingScanPage = location.pathname === '/scan'; - const viewingHistoryPage = location.pathname.includes( '/scan/history' ); - const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] ); - const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] ); - - if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) { - return null; - } - - return ( - <> - - - { __( 'Scanner', 'jetpack-protect' ) } - - - { __( 'History', 'jetpack-protect' ) } - - - - ); -} diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..45a8524e60b59 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,6 +1,7 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; +import { Icon } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; import styles from './styles.module.scss'; @@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } ) return ( <>
    - +
    - { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
    - ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
    - - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
    - ) } - { fixedIn && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
    - ) } - { ! description &&
    { learnMoreButton }
    } -
    - ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
    - -
    - { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
    - { getTitle() } - { hasPlan && ( -
    - { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
    - ) } -
    - { hasPlan ? ( - <> -
    - -
    - - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
    -
    - { ! scanning && ( -
    - ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
    - - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
    - ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
    { filename }
    - - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
    - - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
    - ) } - { ! description &&
    { learnMoreButton }
    } - { [ 'ignored', 'current' ].includes( status ) && ( -
    - { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
    - ) } -
    - ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
    - { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
    - ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
    - { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
    - +
    diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index 2338d306e6780..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx index 1468fb40ba8cf..0dfd22468079e 100644 --- a/projects/plugins/protect/src/js/routes/firewall/index.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx @@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; -import ScanFooter from '../scan/scan-footer'; import FirewallAdminSectionHero from './firewall-admin-section-hero'; import FirewallFooter from './firewall-footer'; import styles from './styles.module.scss'; @@ -576,7 +575,7 @@ const FirewallPage = () => {
    - { wafSupported ? : } + { wafSupported && } ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 141c51cde284a..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previous %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previous threats', 'jetpack-protect' ) } - - - - { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) } - - - - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
    -
    - { getTitle() } -
    - -
    -
    - -
    - ) : ( - <> -
    -
    - -
    -
    -
    - -

    - { __( "Don't worry about a thing", 'jetpack-protect' ) } -

    - - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
    - - ) } - -
    - -
    -
    - ) } - -
    - ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index d30f3e0ac3344..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..c56ae3c747f3e 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,16 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import OnboardingPopover from '../../components/onboarding-popover'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; -import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; +import styles from './styles.module.scss'; /** * Scan Page @@ -19,23 +21,41 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); + const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null ); + let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: [ 'current' ], + operator: 'isAny', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,16 +69,33 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } - + + + +
    + +
    + { !! status && ! isScanInProgress( status ) && ( + + ) } + { !! status && ! isScanInProgress( status ) && hasPlan && ( + + ) } + +
    +
    ); diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx index 0e85aa56d9289..c29af26bcb409 100644 --- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx +++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx @@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan'; const { siteSuffix } = window.jetpackProtectInitialState; -const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' ); -const scanResultsDescription = ( - - { __( - 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files', - 'jetpack-protect' - ) } - -); const UpgradeButton = props => { const { upgradePlan } = usePlan(); const { recordEvent } = useAnalyticsTracks(); @@ -27,11 +18,6 @@ const UpgradeButton = props => { }; export default [ - { - id: 'free-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, - }, { id: 'free-daily-scans', title: __( 'Daily automated scans', 'jetpack-protect' ), @@ -49,10 +35,41 @@ export default [ ), }, + { + id: 'paid-daily-and-manual-scans', + title: __( 'Daily & manual scanning', 'jetpack-protect' ), + description: ( + + { __( + 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', + 'jetpack-protect' + ) } + + ), + }, + { + id: 'free-scan-results', + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.', + 'jetpack-protect' + ) } + + ), + }, { id: 'paid-scan-results', - title: scanResultsTitle, - description: scanResultsDescription, + title: __( 'Your scan results', 'jetpack-protect' ), + description: ( + + { __( + 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.', + 'jetpack-protect' + ) } + + ), }, { id: 'paid-fix-all-threats', @@ -97,16 +114,4 @@ export default [ ), }, - { - id: 'paid-daily-and-manual-scans', - title: __( 'Daily & manual scanning', 'jetpack-protect' ), - description: ( - - { __( - 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.', - 'jetpack-protect' - ) } - - ), - }, ]; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 9e1b9c102a037..db76bac1b15b0 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -1,33 +1,41 @@ import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Tooltip } from '@wordpress/components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useMemo } from 'react'; import AdminSectionHero from '../../components/admin-section-hero'; import ErrorAdminSectionHero from '../../components/error-admin-section-hero'; import OnboardingPopover from '../../components/onboarding-popover'; -import useThreatsList from '../../components/threats-list/use-threats-list'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import useFixers from '../../hooks/use-fixers'; import useModal from '../../hooks/use-modal'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); - const { hasPlan } = usePlan(); + const { recordEvent } = useAnalyticsTracks(); + const { hasPlan, upgradePlan } = usePlan(); + const { setModal } = useModal(); const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); - const { list } = useThreatsList(); const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const { setModal } = useModal(); + + const getScan = useCallback( () => { + recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' ); + upgradePlan(); + }, [ recordEvent, upgradePlan ] ); + + const { globalStats } = useWafData(); + const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities ); + const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities ) + ? '50,000' + : totalVulnerabilities.toLocaleString(); + + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); @@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => { // List of fixable threats that do not have a fix in progress const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); + return status.threats.filter( threat => { + const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id; return ( threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) ); } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); + }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] ); const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } const handleShowAutoFixersClick = threatList => { @@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => { ) : __( 'Most recent results', 'jetpack-protect' ) } - { ! hasPlan && ( - - ) } + 0 ? 'error' : 'success' }> { numThreats > 0 ? sprintf( @@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => { ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) ) - : sprintf( - /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No active %s', 'jetpack-protect' ), - hasPlan - ? __( 'threats', 'jetpack-protect' ) - : __( - 'vulnerabilities', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) - ) } + : __( "Don't worry about a thing", 'jetpack-protect' ) } <> - - { __( - 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', - 'jetpack-protect' - ) } - - { fixableList.length > 0 && ( + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } + + ) : ( <> - - { ! scanning && ( - -
    - -
    } secondary={ } diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 908e34f6e71d7..163fd23248aaa 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,12 +1,14 @@ -.subheading-text { - white-space: nowrap; +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.product-section, .info-section { - margin-top: calc( var( --spacing-base ) * 7 ); // 56px - margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px -} +.scan-results-container { + padding-left: 0; + padding-right: 0; + overflow: hidden; -.auto-fixers { - margin-top: calc( var( --spacing-base ) * 4 ); // 32px -} \ No newline at end of file + > * { + margin-left: calc( var( --spacing-base ) * -3 ); // -24px + margin-right: calc( var( --spacing-base ) * -3 ); // -24px + } +} diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..0c65dfec146a7 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -33,6 +33,24 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + /** + * Transpile @wordpress/dataviews in node_modules too. + * + * @see https://github.com/Automattic/jetpack/issues/39907 + */ + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress/dataviews/' ], + babelOpts: { + configFile: false, + plugins: [ + [ + require.resolve( '@automattic/babel-plugin-replace-textdomain' ), + { textdomain: 'jetpack-protect' }, + ], + ], + }, + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From e5d5b047b30734e5cf874fb97772b42d5fcee8ad Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:22:55 -0800 Subject: [PATCH 163/290] Components: Add ScanReport (#40419) --- .../changelog/components-add-scan-report | 4 + .../components/scan-report/constants.ts | 30 +++ .../components/scan-report/index.tsx | 197 ++++++++++++++++++ .../scan-report/stories/index.stories.tsx | 72 +++++++ .../components/scan-report/styles.module.scss | 21 ++ .../components/shield-icon/index.tsx | 14 +- .../shield-icon/stories/index.stories.tsx | 6 +- projects/js-packages/components/index.ts | 1 + .../scan/changelog/components-add-scan-report | 4 + .../js-packages/scan/src/types/threats.ts | 24 ++- 10 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 projects/js-packages/components/changelog/components-add-scan-report create mode 100644 projects/js-packages/components/components/scan-report/constants.ts create mode 100644 projects/js-packages/components/components/scan-report/index.tsx create mode 100644 projects/js-packages/components/components/scan-report/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/scan-report/styles.module.scss create mode 100644 projects/js-packages/scan/changelog/components-add-scan-report diff --git a/projects/js-packages/components/changelog/components-add-scan-report b/projects/js-packages/components/changelog/components-add-scan-report new file mode 100644 index 0000000000000..ba0fbd4cce025 --- /dev/null +++ b/projects/js-packages/components/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport component diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts new file mode 100644 index 0000000000000..436eed91c5701 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -0,0 +1,30 @@ +import { __ } from '@wordpress/i18n'; +import { + code as fileIcon, + color as themeIcon, + plugins as pluginIcon, + shield as shieldIcon, + wordpress as coreIcon, +} from '@wordpress/icons'; + +export const TYPES = [ + { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, + { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, + { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, + { value: 'files', label: __( 'Files', 'jetpack-components' ) }, +]; + +export const ICONS = { + plugins: pluginIcon, + themes: themeIcon, + core: coreIcon, + files: fileIcon, + default: shieldIcon, +}; + +export const FIELD_ICON = 'icon'; +export const FIELD_TYPE = 'type'; +export const FIELD_NAME = 'name'; +export const FIELD_STATUS = 'status'; +export const FIELD_UPDATE = 'update'; +export const FIELD_VERSION = 'version'; diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx new file mode 100644 index 0000000000000..14795376f7d95 --- /dev/null +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -0,0 +1,197 @@ +import { type ScanReportExtension } from '@automattic/jetpack-scan'; +import { Tooltip } from '@wordpress/components'; +import { + type SupportedLayouts, + type View, + type Field, + DataViews, + filterSortAndPaginate, +} from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; +import { Icon } from '@wordpress/icons'; +import { useCallback, useMemo, useState } from 'react'; +import ShieldIcon from '../shield-icon'; +import { + FIELD_NAME, + FIELD_VERSION, + FIELD_ICON, + FIELD_STATUS, + FIELD_TYPE, + TYPES, + ICONS, +} from './constants'; +import styles from './styles.module.scss'; + +/** + * DataViews component for displaying a scan report. + * + * @param {object} props - Component props. + * @param {Array} props.data - Scan report data. + * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * + * @return {JSX.Element} The ScanReport component. + */ +export default function ScanReport( { data, onChangeSelection } ): JSX.Element { + const baseView = { + search: '', + filters: [], + page: 1, + perPage: 20, + }; + + /** + * DataView default layouts. + * + * This property provides layout information about the view types that are active. If empty, enables all layout types (see “Layout Types”) with empty layout data. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view + */ + const defaultLayouts: SupportedLayouts = { + table: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_TYPE, FIELD_NAME, FIELD_VERSION ], + layout: { + primaryField: FIELD_STATUS, + }, + }, + list: { + ...baseView, + fields: [ FIELD_STATUS, FIELD_VERSION ], + layout: { + primaryField: FIELD_NAME, + mediaField: FIELD_ICON, + }, + }, + }; + + /** + * DataView view object - configures how the dataset is visible to the user. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object + */ + const [ view, setView ] = useState< View >( { + type: 'table', + ...defaultLayouts.table, + } ); + + /** + * DataView fields - describes the visible items for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object + */ + const fields = useMemo( () => { + const iconHeight = 20; + const result: Field< ScanReportExtension >[] = [ + { + id: FIELD_STATUS, + label: __( 'Status', 'jetpack-components' ), + render( { item }: { item: ScanReportExtension } ) { + let variant: 'info' | 'warning' | 'success' = 'info'; + let text = __( + 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', + 'jetpack-components' + ); + + if ( item.checked ) { + if ( item.threats.length > 0 ) { + variant = 'warning'; + text = __( 'Threat detected.', 'jetpack-components' ); + } else { + variant = 'success'; + text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + } + } + + return ( + +
    + +
    +
    + ); + }, + }, + { + id: FIELD_TYPE, + label: __( 'Type', 'jetpack-components' ), + elements: TYPES, + }, + { + id: FIELD_NAME, + label: __( 'Name', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.name ? item.name : ''; + }, + }, + { + id: FIELD_VERSION, + label: __( 'Version', 'jetpack-components' ), + enableGlobalSearch: true, + getValue( { item }: { item: ScanReportExtension } ) { + return item.version ? item.version : ''; + }, + }, + ...( view.type === 'list' + ? [ + { + id: FIELD_ICON, + label: __( 'Icon', 'jetpack-components' ), + enableSorting: false, + enableHiding: false, + getValue( { item }: { item: ScanReportExtension } ) { + return ICONS[ item.type ] || ''; + }, + render( { item }: { item: ScanReportExtension } ) { + return ( +
    + +
    + ); + }, + }, + ] + : [] ), + ]; + + return result; + }, [ view ] ); + + /** + * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. + * + * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts + */ + const { data: processedData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, fields ); + }, [ data, view, fields ] ); + + /** + * Callback function to update the view state. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function + */ + const onChangeView = useCallback( ( newView: View ) => { + setView( newView ); + }, [] ); + + /** + * DataView getItemId function - returns the unique ID for each record in the dataset. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function + */ + const getItemId = useCallback( ( item: ScanReportExtension ) => item.id.toString(), [] ); + + return ( + + ); +} diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx new file mode 100644 index 0000000000000..63926908850de --- /dev/null +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -0,0 +1,72 @@ +import ScanReport from '..'; + +export default { + title: 'JS Packages/Components/Scan Report', + component: ScanReport, + parameters: { + backgrounds: { + default: 'light', + values: [ { name: 'light', value: 'white' } ], + }, + }, + decorators: [ + Story => ( +
    + +
    + ), + ], +}; + +export const Default = args => ; +Default.args = { + data: [ + { + id: 1, + name: 'WordPress', + slug: null, + version: '6.7.1', + threats: [], + checked: true, + type: 'core', + }, + { + id: 2, + name: 'Jetpack', + slug: 'jetpack/jetpack.php', + version: '14.1-a.7', + threats: [], + checked: false, + type: 'plugins', + }, + { + id: 3, + name: 'Twenty Fifteen', + slug: 'twentyfifteen', + version: '1.1', + threats: [ + { + id: 198352527, + signature: 'Vulnerable.WP.Extension', + description: 'Vulnerable WordPress extension', + severity: 3, + }, + ], + checked: true, + type: 'themes', + }, + { + id: 4, + threats: [ + { + id: 198352406, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, + ], + checked: true, + type: 'files', + }, + ], +}; diff --git a/projects/js-packages/components/components/scan-report/styles.module.scss b/projects/js-packages/components/components/scan-report/styles.module.scss new file mode 100644 index 0000000000000..d313d4cb8898a --- /dev/null +++ b/projects/js-packages/components/components/scan-report/styles.module.scss @@ -0,0 +1,21 @@ +@import '@wordpress/dataviews/build-style/style.css'; + +.threat__media { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #EDFFEE; + border-color: #EDFFEE; + + svg { + fill: var( --jp-black ); + } +} + +.tooltip { + max-width: 240px; + border-radius: 4px; + text-align: left; +} \ No newline at end of file diff --git a/projects/js-packages/components/components/shield-icon/index.tsx b/projects/js-packages/components/components/shield-icon/index.tsx index fee9f4d70c463..b07b943b5e7fa 100644 --- a/projects/js-packages/components/components/shield-icon/index.tsx +++ b/projects/js-packages/components/components/shield-icon/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; const COLORS = { - error: '#D63638', - warning: '#F0B849', - success: '#069E08', default: '#1d2327', + info: '#A7AAAD', + success: '#069E08', + warning: '#F0B849', + error: '#D63638', }; /** @@ -32,11 +33,11 @@ export default function ShieldIcon( { }: { className?: string; contrast?: string; - fill?: 'default' | 'success' | 'warning' | 'error' | string; + fill?: 'default' | 'info' | 'success' | 'warning' | 'error' | string; height?: number; icon?: 'success' | 'error'; outline?: boolean; - variant: 'default' | 'success' | 'warning' | 'error'; + variant: 'default' | 'info' | 'success' | 'warning' | 'error'; } ): JSX.Element { const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ]; const iconFill = outline ? shieldFill : contrast; @@ -60,6 +61,9 @@ export default function ShieldIcon( { } fill={ shieldFill } /> + { 'info' === iconVariant && ( + + ) } { 'success' === iconVariant && ( {
    +
    + diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index 4b0f3612012e7..6df50ee7fdb61 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -48,6 +48,7 @@ export { default as ThreatFixerButton } from './components/threat-fixer-button'; export { default as ThreatSeverityBadge } from './components/threat-severity-badge'; export { default as ThreatsDataViews } from './components/threats-data-views'; export { default as ShieldIcon } from './components/shield-icon'; +export { default as ScanReport } from './components/scan-report'; export { default as Text, H2, H3, Title } from './components/text'; export { default as ToggleControl } from './components/toggle-control'; export { default as numberFormat } from './components/number-format'; diff --git a/projects/js-packages/scan/changelog/components-add-scan-report b/projects/js-packages/scan/changelog/components-add-scan-report new file mode 100644 index 0000000000000..eeb9c55de4a28 --- /dev/null +++ b/projects/js-packages/scan/changelog/components-add-scan-report @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates/adds scan types diff --git a/projects/js-packages/scan/src/types/threats.ts b/projects/js-packages/scan/src/types/threats.ts index 72428c209ee49..22f6b06477163 100644 --- a/projects/js-packages/scan/src/types/threats.ts +++ b/projects/js-packages/scan/src/types/threats.ts @@ -4,6 +4,23 @@ export type ThreatStatus = 'fixed' | 'ignored' | 'current'; export type ThreatFixType = 'replace' | 'delete' | 'update' | string; +export type ScanReportExtension = { + id: number; + checked: boolean; + slug?: string; + name?: string; + version?: string; + threats: Threat[]; + type: 'plugins' | 'themes' | 'core' | 'files'; +}; + +export type Extension = { + slug: string; + name: string; + version: string; + type: 'plugins' | 'themes' | 'core'; +}; + export type Threat = { /** The threat's unique ID. */ id: string | number; @@ -57,10 +74,5 @@ export type Threat = { diff?: string; /** The affected extension. */ - extension?: { - slug: string; - name: string; - version: string; - type: 'plugins' | 'themes' | 'core'; - }; + extension?: Extension; }; From 7ef880964addac8129439082ee068fc74e67cb9b Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 6 Dec 2024 12:05:11 -0700 Subject: [PATCH 164/290] Fix type errors Protect: add HMR support Revert "Protect: add HMR support" This reverts commit 06497a05bb050c86e097b36038c8742af427388d. --- projects/js-packages/scan/src/utils/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 9e2e75bcd4d91..30a96cbd132d5 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,9 +21,9 @@ export const getThreatIcon = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return 'wordpress-alt'; - case 'plugin': + case 'plugins': return 'plugins'; - case 'theme': + case 'themes': return 'appearance'; case 'file': return 'media-code'; @@ -36,9 +36,9 @@ export const getThreatSubtitle = ( threat: Threat ) => { switch ( getThreatType( threat ) ) { case 'core': return __( 'Vulnerable WordPress Version', 'jetpack-scan' ); - case 'plugin': + case 'plugins': return __( 'Vulnerable Plugin', 'jetpack-scan' ); - case 'theme': + case 'themes': return __( 'Vulnerable Theme', 'jetpack-scan' ); case 'file': return __( 'File Threat', 'jetpack-scan' ); From 55a8d4fa0cb8d824a4027607d4febf7b4be2b043 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 9 Dec 2024 14:38:56 -0700 Subject: [PATCH 165/290] Protect: Refactor AdminSectionHero (#40516) --- .../components/admin-section-hero/index.tsx | 96 +++++----- .../stories/index.stories.jsx | 16 +- .../admin-section-hero/styles.module.scss | 44 +++-- .../error-admin-section-hero/index.tsx | 30 ++- .../styles.module.scss | 6 +- .../firewall/firewall-admin-section-hero.tsx | 27 ++- .../js/routes/firewall/firewall-statcards.jsx | 2 +- .../src/js/routes/firewall/styles.module.scss | 31 ++- .../routes/scan/scan-admin-section-hero.tsx | 177 +++++++++--------- .../scan/scanning-admin-section-hero.tsx | 70 ++++--- .../src/js/routes/scan/styles.module.scss | 10 + 11 files changed, 278 insertions(+), 231 deletions(-) diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx index 5ccf607698084..7638936db5139 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/index.tsx @@ -2,67 +2,73 @@ import { AdminSectionHero as JetpackAdminSectionHero, H3, ShieldIcon, + Container, + Col, } from '@automattic/jetpack-components'; -import SeventyFiveLayout from '../seventy-five-layout'; +import clsx from 'clsx'; import AdminSectionHeroNotices from './admin-section-hero-notices'; import styles from './styles.module.scss'; -interface AdminSectionHeroProps { - main: React.ReactNode; - secondary?: React.ReactNode; - preserveSecondaryOnMobile?: boolean; - spacing?: number; -} - -interface AdminSectionHeroComponent extends React.FC< AdminSectionHeroProps > { - Heading: React.FC< { - children: React.ReactNode; - showIcon?: boolean; - variant?: 'default' | 'success' | 'error'; - outline?: boolean; - } >; - Subheading: React.FC< { children: React.ReactNode } >; -} - -const AdminSectionHero: AdminSectionHeroComponent = ( { - main, - secondary, - preserveSecondaryOnMobile = true, - spacing = 7, -} ) => { +const AdminSectionHero = ( { + children, + ...props +}: React.ComponentProps< typeof JetpackAdminSectionHero > ) => { return ( - + - + + +
    { children }
    + +
    ); }; -AdminSectionHero.Heading = ( { +AdminSectionHero.Main = ( { children, - variant = 'default', - showIcon = false, + className, + ...props }: { children: React.ReactNode; - variant?: 'default' | 'success' | 'error'; - showIcon?: boolean; + className?: string; + [ key: string ]: unknown; +} ) => { + return ( +
    + { children } +
    + ); +}; + +AdminSectionHero.Aside = ( { + children, + className, + ...props +}: React.ComponentProps< 'div' > & { + className?: string; } ) => { return ( -

    +
    { children } - { showIcon && ( +
    + ); +}; + +AdminSectionHero.Heading = ( { + children, + icon, + ...props +}: React.ComponentProps< typeof H3 > & { + icon?: 'default' | 'success' | 'error'; +} ) => { + return ( +

    + { children } + { !! icon && ( { - return
    { children }
    ; -}; - export default AdminSectionHero; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx index ca2dfda7fc98e..59ed9086d6317 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx @@ -9,16 +9,16 @@ export default { export const Default = args => ; Default.args = { - main: ( + children: ( <> - - - { 'No threats found' } - - + + + { 'No threats found' } { 'Most recent results' } - + + + + ), - secondary: , }; diff --git a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss index a414aa9216f5c..74cfe29aaaded 100644 --- a/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/admin-section-hero/styles.module.scss @@ -1,15 +1,39 @@ -.header-main { +.admin-section-hero { display: flex; flex-direction: column; - justify-content: center; - align-items: flex-start; + gap: calc( var( --spacing-base ) * 6 ); // 48px + + max-width: var(--max-container-width); + padding: calc( var( --spacing-base ) * 6 ) 0; // 48px 0 + margin: 0 auto; + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media (min-width: 600px) { + padding: calc( var( --spacing-base ) * 7 ) 0; // 56px 0 + } + + @media ( min-width: 1100px ) { + flex-direction: row; + align-items: center; + gap: calc( var( --spacing-base ) * 3 ); // 24px + } } -.header-secondary { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; +.admin-section-hero__main { + flex: 2; +} + +.admin-section-hero__aside { + flex: 1; + flex-shrink: 0; + + @media ( min-width: 1200px ) { + display: flex; + justify-content: flex-end; + } } .heading-icon { @@ -17,10 +41,6 @@ margin-bottom: calc( var( --spacing-base ) / 2 * -1 ); // -4px } -.subheading { - width: fit-content; -} - .connection-error-col { margin-top: calc( var( --spacing-base ) * 3 + 1px ); // 25px } diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx index 1a9bc87387fa9..536d8f50de7d1 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx @@ -1,6 +1,5 @@ -import { Text } from '@automattic/jetpack-components'; +import { ShieldIcon, Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; import styles from './styles.module.scss'; @@ -19,22 +18,17 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { displayErrorMessage += ' ' + __( 'Try again in a few minutes.', 'jetpack-protect' ); return ( - - -
    - - { __( 'An error occurred', 'jetpack-protect' ) } -
    -
    - - { displayErrorMessage } - - - } - preserveSecondaryOnMobile={ false } - /> + + + +
    + { __( 'An error occurred', 'jetpack-protect' ) } + +
    +
    + { displayErrorMessage } +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss index 6f0750abd02f8..1c89377d4b4b5 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss @@ -4,11 +4,7 @@ } .warning { - width: 54px; - height: 54px; - fill: var( --jp-red ); - margin-left: -8px; - margin-right: var( --spacing-base ); // 8px + margin-left: calc( var( --spacing-base ) * 1.5 ); // 12px } .scan-navigation { diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 3f70a75509b76..837f649c67f16 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -5,6 +5,7 @@ import AdminSectionHero from '../../components/admin-section-hero'; import useWafData from '../../hooks/use-waf-data'; import FirewallStatCards from './firewall-statcards'; import FirewallSubheading from './firewall-subheading'; +import styles from './styles.module.scss'; const FirewallAdminSectionHero = () => { const { @@ -84,16 +85,22 @@ const FirewallAdminSectionHero = () => { }, [ status ] ); return ( - - - { heading } - { subheading } - - } - secondary={ wafSupported && } - /> + + + + { heading } + { subheading } + + { wafSupported && ( + + + + ) } + ); }; diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 15c80df763c18..1eebd67cb60d7 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -93,7 +93,7 @@ const FirewallStatCards = () => { ); return ( -
    +
    diff --git a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss index afcbc2ad69b30..9404db7b56f09 100644 --- a/projects/plugins/protect/src/js/routes/firewall/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/firewall/styles.module.scss @@ -3,6 +3,10 @@ max-width: calc( 744px + ( var( --spacing-base ) * 6 ) ); // 744px + 48px (desired inner width + horizontal padding) } +.status { + margin-bottom: calc( var( --spacing-base ) * 2 ); // 16px +} + .toggle-section { display: flex; @@ -145,14 +149,10 @@ align-items: center; } -.stat-card-wrapper { +.stat-cards-wrapper { display: flex; - margin-left: auto; - flex-wrap: wrap; - - >:first-child { - margin-right: calc( var( --spacing-base ) * 3 ); // 24px - } + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 3 ); // 24px .disabled { opacity: 0.5; @@ -220,6 +220,23 @@ background-color: var( --jp-white-off ); } +@media ( max-width: 1200px ) { + .stat-cards-wrapper { + justify-content: flex-start; + } +} + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} + .standalone-mode, .share-data { display: flex; flex-direction: column; diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index db76bac1b15b0..4257c585351eb 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -59,6 +59,28 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } + let heading = __( "Don't worry about a thing", 'jetpack-protect' ); + if ( numThreats > 0 ) { + if ( hasPlan ) { + heading = sprintf( + /* translators: %s: Total number of threats */ + _n( '%1$s active threat', '%1$s active threats', numThreats, 'jetpack-protect' ), + numThreats + ); + } else { + heading = sprintf( + /* translators: %s: Total number of vulnerabilities */ + _n( + '%1$s active vulnerability', + '%1$s active vulnerabilities', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + } + const handleShowAutoFixersClick = threatList => { return event => { event.preventDefault(); @@ -84,94 +106,79 @@ const ScanAdminSectionHero: React.FC = () => { } return ( - - - { lastCheckedLocalTimestamp - ? sprintf( - // translators: %s: date and time of the last scan - __( '%s results', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) - ) - : __( 'Most recent results', 'jetpack-protect' ) } + + + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS, g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + + 0 ? 'error' : 'success' }> + { heading } + + { hasPlan ? ( + + { __( + "We actively review your site's files line-by-line to identify threats and vulnerabilities.", + 'jetpack-protect' + ) } - - 0 ? 'error' : 'success' }> - { numThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s active %2$s', 'jetpack-protect' ), - numThreats, - hasPlan - ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) - : _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' ) - ) - : __( "Don't worry about a thing", 'jetpack-protect' ) } - - - <> - { hasPlan ? ( - - { __( - "We actively review your site's files line-by-line to identify threats and vulnerabilities.", - 'jetpack-protect' - ) } - - ) : ( - <> - - { sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - + ) : ( + <> + + { sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted ) } - { fixableList.length > 0 && ( - <> -
    - -
    -
    + -
    - - } - /> + > + + + + ) } + { fixableList.length > 0 && ( + <> +
    + +
    +
    +
    ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx index 4db4449b60119..ac9e0137cd170 100644 --- a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx @@ -18,44 +18,38 @@ const ScanningAdminSectionHero: React.FC = () => { : totalVulnerabilities.toLocaleString(); return ( - - - { __( 'Your results will be ready soon', 'jetpack-protect' ) } - - - <> - { hasPlan && ( - - ) } - - { hasPlan - ? __( - "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", - 'jetpack-protect' - ) - : sprintf( - // translators: placeholder is the number of total vulnerabilities i.e. "22,000". - __( - 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', - 'jetpack-protect' - ), - totalVulnerabilitiesFormatted - ) } - - - - - } - secondary={ } - preserveSecondaryOnMobile={ false } - spacing={ 4 } - /> + + + + { __( 'Your results will be ready soon', 'jetpack-protect' ) } + + { hasPlan && ( + + ) } + + { hasPlan + ? __( + "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.", + 'jetpack-protect' + ) + : sprintf( + // translators: placeholder is the number of total vulnerabilities i.e. "22,000". + __( + 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.', + 'jetpack-protect' + ), + totalVulnerabilitiesFormatted + ) } + + + + + + ); }; diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 163fd23248aaa..5806ca5353863 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,3 +1,7 @@ +.scanning-main { + max-width: 512px; +} + .auto-fixers { margin-top: calc( var( --spacing-base ) * 4 ); // 32px } @@ -12,3 +16,9 @@ margin-right: calc( var( --spacing-base ) * -3 ); // -24px } } + +.progress-animation { + @media (max-width: 1099px) { + display: none; + } +} From f33e209b4a72a2323c2df5399211c8d31637faee Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:04:08 -0800 Subject: [PATCH 166/290] Protect: Update Scan History extension types (#40548) --- .../protect/src/class-scan-history.php | 4 +++ .../js/routes/firewall/firewall-footer.jsx | 2 -- .../src/js/routes/firewall/styles.module.scss | 30 ------------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index 8ea1dec7156e7..23019ccd634ad 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -219,6 +219,10 @@ private static function normalize_api_data( $scan_data ) { } foreach ( $scan_data->threats as $source_threat ) { + if ( ! empty( $source_threat->extension ) && in_array( $source_threat->extension->type, array( 'plugin', 'theme' ), true ) ) { + $source_threat->extension->type .= 's'; + } + $history->threats[] = new Threat_Model( $source_threat ); } diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx index 0e28d7bae7c98..0c175b1cd651f 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx @@ -69,7 +69,6 @@ const ShareData = () => {
    { __( 'Share data with Jetpack', 'jetpack-protect' ) } { ) } /> :first-child { - margin-right: 0; - margin-bottom: var( --spacing-base ); // 8px - } - } - - .stat-card-icon { - margin-bottom: 0; - } -} - -.share-data-section { - display: flex; - - .share-data-toggle { - margin-top: calc( var( --spacing-base ) / 2 ); // 4px - margin-right: var( --spacing-base ); // 8px - } -} - .icon-tooltip { max-height: 20px; margin-left: calc( var( --spacing-base ) / 2 ); // 4px From 94c9c9ab7fe9a9868e76d9b32f79b9325dbbba0f Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:25:25 -0800 Subject: [PATCH 167/290] Protect: Add Home page (#40317) * Init project branch * Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller * Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller * Protect: de-emphasize cloud link by using link variant (#40211) * Protect: add ShieldIcon component * Protect: Add ShieldIcon Component (#40402) * Protect: Integrate ThreatsDataViews Component (#40076) * Components: Add ScanReport (#40419) * Fix type errors * Protect: add home page --------- Co-authored-by: Nate Weller Co-authored-by: Nate Weller Co-authored-by: Dean Kmyta --- .../add-hide-value-prop-to-stat-card | 4 + .../components/components/stat-card/index.tsx | 13 +- .../components/components/stat-card/types.ts | 5 + .../protect/changelog/add-protect-home | 4 + .../protect/src/class-jetpack-protect.php | 3 +- .../src/js/components/admin-page/index.jsx | 1 + .../src/js/components/pricing-table/index.jsx | 2 +- .../components/seventy-five-layout/index.tsx | 73 ----- .../seventy-five-layout/styles.module.scss | 13 - .../plugins/protect/src/js/hooks/use-plan.tsx | 2 +- projects/plugins/protect/src/js/index.tsx | 4 +- .../routes/home/home-admin-section-hero.tsx | 50 ++++ .../src/js/routes/home/home-statcards.jsx | 274 ++++++++++++++++++ .../protect/src/js/routes/home/index.jsx | 25 ++ .../src/js/routes/home/styles.module.scss | 69 +++++ 15 files changed, 449 insertions(+), 93 deletions(-) create mode 100644 projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card create mode 100644 projects/plugins/protect/changelog/add-protect-home delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss create mode 100644 projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx create mode 100644 projects/plugins/protect/src/js/routes/home/home-statcards.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/styles.module.scss diff --git a/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card new file mode 100644 index 0000000000000..0d4002c768dd8 --- /dev/null +++ b/projects/js-packages/components/changelog/add-hide-value-prop-to-stat-card @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Stat Card: add hideValue prop diff --git a/projects/js-packages/components/components/stat-card/index.tsx b/projects/js-packages/components/components/stat-card/index.tsx index b6854dc02f37e..222cafb44068e 100644 --- a/projects/js-packages/components/components/stat-card/index.tsx +++ b/projects/js-packages/components/components/stat-card/index.tsx @@ -18,7 +18,14 @@ import type React from 'react'; * @param {StatCardProps} props - Component props. * @return {React.ReactNode} - StatCard react component. */ -const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCardProps ) => { +const StatCard = ( { + className, + icon, + label, + value, + variant = 'square', + hideValue = false, +}: StatCardProps ) => { const formattedValue = numberFormat( value ); const compactValue = numberFormat( value, { notation: 'compact', @@ -33,12 +40,12 @@ const StatCard = ( { className, icon, label, value, variant = 'square' }: StatCa { variant === 'square' ? ( - { compactValue } + { hideValue ? '-' : compactValue } ) : ( - { formattedValue } + { hideValue ? '-' : formattedValue } ) }
    diff --git a/projects/js-packages/components/components/stat-card/types.ts b/projects/js-packages/components/components/stat-card/types.ts index 4b0fd698e6774..8e1c0e99d6d60 100644 --- a/projects/js-packages/components/components/stat-card/types.ts +++ b/projects/js-packages/components/components/stat-card/types.ts @@ -25,4 +25,9 @@ export type StatCardProps = { * @default 'square' */ variant?: 'square' | 'horizontal'; + + /** + * Whether to hide the value. + */ + hideValue?: boolean; }; diff --git a/projects/plugins/protect/changelog/add-protect-home b/projects/plugins/protect/changelog/add-protect-home new file mode 100644 index 0000000000000..0bcfedb6fe8ac --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds a Home page and StatCards diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 293ccdaeb3ce7..492cded402990 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -457,8 +457,9 @@ public static function get_waf_stats() { } return array( - 'blockedRequests' => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false, + 'blockedRequests' => Waf_Stats::get_blocked_requests(), 'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(), + 'blockedLogins' => (int) get_option( 'jetpack_protect_blocked_attempts', 0 ), ); } } diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 68f9359a9bd81..5811238cd266e 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -63,6 +63,7 @@ const AdminPage = ( { children } ) => { { notice && } + { const getProtectFree = useCallback( async () => { recordEvent( 'jetpack_protect_connected_product_activated' ); await connectSiteMutation.mutateAsync(); - navigate( '/scan' ); + navigate( '/' ); }, [ connectSiteMutation, recordEvent, navigate ] ); const args = { diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx b/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx deleted file mode 100644 index 19ee4309e55a5..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Container, Col, useBreakpointMatch } from '@automattic/jetpack-components'; -import React from 'react'; - -// Define the props interface for the SeventyFiveLayout component -interface SeventyFiveLayoutProps { - spacing?: number; - gap?: number; - main: React.ReactNode; - mainClassName?: string; - secondary: React.ReactNode; - secondaryClassName?: string; - preserveSecondaryOnMobile?: boolean; - fluid?: boolean; -} - -/** - * SeventyFive layout meta component - * The component name references to - * the sections disposition of the layout. - * FiftyFifty, 75, thus 7|5 means the cols numbers - * for main and secondary sections respectively, - * in large lg viewport size. - * - * @param {object} props - Component props - * @param {number} props.spacing - Horizontal spacing - * @param {number} props.gap - Horizontal gap - * @param {React.ReactNode} props.main - Main section component - * @param {string} props.mainClassName - Main section class name - * @param {React.ReactNode} props.secondary - Secondary section component - * @param {string} props.secondaryClassName - Secondary section class name - * @param {boolean} props.preserveSecondaryOnMobile - Whether to show secondary section on mobile - * @param {boolean} props.fluid - Whether to use fluid layout - * @return {React.ReactNode} - React meta-component - */ -const SeventyFiveLayout: React.FC< SeventyFiveLayoutProps > = ( { - spacing = 0, - gap = 0, - main, - mainClassName, - secondary, - secondaryClassName, - preserveSecondaryOnMobile = false, - fluid, -} ) => { - // Ensure the correct typing for useBreakpointMatch - const [ isSmall, isLarge ] = useBreakpointMatch( [ 'sm', 'lg' ] ); - - /* - * By convention, secondary section is not shown when: - * - preserveSecondaryOnMobile is false - * - on mobile breakpoint (sm) - */ - const hideSecondarySection = ! preserveSecondaryOnMobile && isSmall; - - return ( - - { ! hideSecondarySection && ( - <> - - { main } - - { isLarge && } - - { secondary } - - - ) } - { hideSecondarySection && { main } } - - ); -}; - -export default SeventyFiveLayout; diff --git a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss b/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss deleted file mode 100644 index 5405c6e28a9b4..0000000000000 --- a/projects/plugins/protect/src/js/components/seventy-five-layout/styles.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -// seventy-five layout -// Handle large lg size from here, -// adding a gap on one column -// in between main and secondary sections. -@media ( min-width: 960px ) { - .main { - grid-column: 1 / span 6; - } - - .secondary { - grid-column: 8 / span 5; - } -} diff --git a/projects/plugins/protect/src/js/hooks/use-plan.tsx b/projects/plugins/protect/src/js/hooks/use-plan.tsx index b5ab18da01875..f5cd1d54943b9 100644 --- a/projects/plugins/protect/src/js/hooks/use-plan.tsx +++ b/projects/plugins/protect/src/js/hooks/use-plan.tsx @@ -48,7 +48,7 @@ export default function usePlan( { redirectUrl }: { redirectUrl?: string } = {} const { run: checkout } = useProductCheckoutWorkflow( { productSlug: JETPACK_SCAN_SLUG, - redirectUrl: redirectUrl || adminUrl, + redirectUrl: redirectUrl || adminUrl + '#/scan', siteProductAvailabilityHandler: API.checkPlan, useBlogIdSuffix: true, connectAfterCheckout: false, diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index 2b91f4b090b92..4438d5021a664 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -11,6 +11,7 @@ import { NoticeProvider } from './hooks/use-notices'; import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; +import HomeRoute from './routes/home'; import ScanRoute from './routes/scan'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -56,6 +57,7 @@ function render() { } /> + } /> } /> } /> - } /> + } /> diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx new file mode 100644 index 0000000000000..12d887e933f43 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -0,0 +1,50 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import AdminSectionHero from '../../components/admin-section-hero'; +import usePlan from '../../hooks/use-plan'; +import HomeStatCards from './home-statcards'; +import styles from './styles.module.scss'; + +const HomeAdminSectionHero: React.FC = () => { + const { hasPlan } = usePlan(); + const navigate = useNavigate(); + const handleScanReportClick = useCallback( () => { + navigate( '/scan' ); + }, [ navigate ] ); + + return ( + + + <> + + { __( 'Your site is safe with us', 'jetpack-protect' ) } + + + { hasPlan + ? __( + 'We stay ahead of security threats to keep your site protected.', + 'jetpack-protect' + ) + : __( + 'We stay ahead of security vulnerabilities to keep your site protected.', + 'jetpack-protect' + ) } + + + + + { } + + ); +}; + +export default HomeAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx new file mode 100644 index 0000000000000..2d1dc34cac147 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx @@ -0,0 +1,274 @@ +import { Text, useBreakpointMatch, StatCard, ShieldIcon } from '@automattic/jetpack-components'; +import { Spinner, Tooltip } from '@wordpress/components'; +import { dateI18n } from '@wordpress/date'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; +import usePlan from '../../hooks/use-plan'; +import useWafData from '../../hooks/use-waf-data'; +import styles from './styles.module.scss'; + +const IconWithLabel = ( { label, isSmall, icon } ) => ( + + { icon } + { ! isSmall && ( + + { label } + + ) } + +); + +const HomeStatCard = ( { text, args } ) => ( + +
    + +
    +
    +); + +const HomeStatCards = () => { + const ICON_HEIGHT = 20; + + const { hasPlan } = usePlan(); + const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); + + const { data: status } = useScanStatusQuery(); + const scanning = isScanInProgress( status ); + const numThreats = status.threats.length; + const scanError = status.error; + + let lastCheckedLocalTimestamp = null; + if ( status.lastChecked ) { + // Convert the lastChecked UTC date to a local timestamp + lastCheckedLocalTimestamp = dateI18n( + 'F jS g:i A', + new Date( status.lastChecked + ' UTC' ).getTime(), + false + ); + } + + const { + config: { bruteForceProtection: isBruteForceModuleEnabled }, + isEnabled: isWafModuleEnabled, + wafSupported, + stats, + } = useWafData(); + + const { + blockedRequests: { allTime: allTimeBlockedRequestsCount = 0 } = {}, + blockedLogins: allTimeBlockedLoginsCount = 0, + } = stats || {}; + + const variant = useMemo( () => ( isSmall ? 'horizontal' : 'square' ), [ isSmall ] ); + + const lastCheckedMessage = useMemo( () => { + if ( scanning ) { + return __( 'Your results will be ready soon.', 'jetpack-protect' ); + } + + if ( scanError ) { + return __( + 'Please check your connection or try scanning again in a few minutes.', + 'jetpack-protect' + ); + } + + if ( lastCheckedLocalTimestamp ) { + if ( numThreats > 0 ) { + if ( hasPlan ) { + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d threat.', + 'Last checked on %1$s: We found %2$d threats.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %1$s: date/time, %2$d: number + _n( + 'Last checked on %1$s: We found %2$d vulnerability.', + 'Last checked on %1$s: We found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + lastCheckedLocalTimestamp, + numThreats + ); + } + return sprintf( + // translators: %s: date/time + __( 'Last checked on %s: Your site is secure.', 'jetpack-protect' ), + lastCheckedLocalTimestamp + ); + } + if ( hasPlan ) { + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %d threat.', + 'Last scan we found %d threats.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + } + return sprintf( + // translators: %d: number + _n( + 'Last scan we found %2$d vulnerability.', + 'Last scan we found %2$d vulnerabilities.', + numThreats, + 'jetpack-protect' + ), + numThreats + ); + }, [ scanError, scanning, numThreats, lastCheckedLocalTimestamp, hasPlan ] ); + + const scanArgs = useMemo( () => { + let scanIcon; + if ( scanning ) { + scanIcon = ; + } else if ( scanError ) { + scanIcon = ; + } else { + scanIcon = ( + + ); + } + + let scanLabel; + if ( scanning ) { + scanLabel = __( 'One moment, please…', 'jetpack-protect' ); + } else if ( scanError ) { + scanLabel = __( 'An error occurred', 'jetpack-protect' ); + } else if ( hasPlan ) { + scanLabel = _n( 'Threat identified', 'Threats identified', numThreats, 'jetpack-protect' ); + } else { + scanLabel = _n( + 'Vulnerability identified', + 'Vulnerabilities identified', + numThreats, + 'jetpack-protect' + ); + } + + return { + variant, + icon: ( + + ), + label: { scanLabel }, + value: numThreats, + hideValue: !! ( scanError || scanning ), + }; + }, [ variant, scanning, ICON_HEIGHT, scanError, numThreats, hasPlan, isSmall ] ); + + const wafArgs = useMemo( + () => ( { + variant: variant, + className: isWafModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Firewall', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked requests', 'jetpack-protect' ) } + + ), + value: allTimeBlockedRequestsCount, + hideValue: ! isWafModuleEnabled, + } ), + [ variant, isWafModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedRequestsCount ] + ); + + const bruteForceArgs = useMemo( + () => ( { + variant: variant, + className: isBruteForceModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + { ! isSmall && ( + + { __( 'Brute force', 'jetpack-protect' ) } + + ) } + + ), + label: ( + + { __( 'Blocked login attempts', 'jetpack-protect' ) } + + ), + value: allTimeBlockedLoginsCount, + hideValue: ! isBruteForceModuleEnabled, + } ), + [ variant, isBruteForceModuleEnabled, ICON_HEIGHT, isSmall, allTimeBlockedLoginsCount ] + ); + + return ( +
    + + { wafSupported && ( + + ) } + +
    + ); +}; + +export default HomeStatCards; diff --git a/projects/plugins/protect/src/js/routes/home/index.jsx b/projects/plugins/protect/src/js/routes/home/index.jsx new file mode 100644 index 0000000000000..718349caaac3f --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/index.jsx @@ -0,0 +1,25 @@ +import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import AdminPage from '../../components/admin-page'; +import HomeAdminSectionHero from './home-admin-section-hero'; + +/** + * Home Page + * + * The entry point for the Home page. + * + * @return {Component} The root component for the scan page. + */ +const HomePage = () => { + return ( + + + + + { /* TODO: Add ScanReport component here */ } + + + + ); +}; + +export default HomePage; diff --git a/projects/plugins/protect/src/js/routes/home/styles.module.scss b/projects/plugins/protect/src/js/routes/home/styles.module.scss new file mode 100644 index 0000000000000..b99bead52dbdb --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/styles.module.scss @@ -0,0 +1,69 @@ +.product-section, .info-section { + margin-top: calc( var( --spacing-base ) * 7 ); // 56px + margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px +} + +.view-scan-report { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px +} + +.stat-cards-wrapper { + display: flex; + justify-content: flex-start; + + > *:not( last-child ) { + margin-right: calc( var( --spacing-base ) * 3 ); // 24px + } + + .disabled { + opacity: 0.5; + } +} + +.stat-card-icon { + width: 100%; + margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px + display: flex; + align-items: center; + gap: 8px; + + svg { + margin: 0; + } + + .active { + fill: var( --jp-green-40 ); + } + + .warning { + fill: var( --jp-yellow-40 ); + } + + .disabled { + fill: var( --jp-gray-40 ); + } + + &-label { + color: var( --jp-black ); + white-space: nowrap; + } +} + +.stat-card-tooltip { + margin-top: 8px; + max-width: 240px; + border-radius: 4px; + text-align: left; +} + + +@media ( max-width: 599px ) { + .stat-cards-wrapper { + flex-direction: column; + gap: var( --spacing-base ); // 8px + } + + .stat-card-icon { + margin-bottom: 0; + } +} \ No newline at end of file From 88960dee27d2a42b0408e50346f56e604036efbc Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:17:14 -0800 Subject: [PATCH 168/290] Protect: Integrate ScanReport (#40420) --- .../components/scan-report/constants.ts | 6 +++ .../components/scan-report/index.tsx | 48 +++++++++++++++++-- .../scan-report/stories/index.stories.tsx | 7 +++ .../threats-data-views/constants.ts | 1 + .../components/threats-data-views/index.tsx | 13 +---- .../update-protect-add-scan-report-to-home | 4 ++ .../firewall/firewall-admin-section-hero.tsx | 4 +- .../js/routes/firewall/firewall-statcards.jsx | 22 ++++----- .../routes/home/home-admin-section-hero.tsx | 3 +- .../protect/src/js/routes/home/index.jsx | 26 ++++++++-- .../src/js/routes/home/styles.module.scss | 13 +++-- 11 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-add-scan-report-to-home diff --git a/projects/js-packages/components/components/scan-report/constants.ts b/projects/js-packages/components/components/scan-report/constants.ts index 436eed91c5701..6a10d008b876f 100644 --- a/projects/js-packages/components/components/scan-report/constants.ts +++ b/projects/js-packages/components/components/scan-report/constants.ts @@ -7,6 +7,12 @@ import { wordpress as coreIcon, } from '@wordpress/icons'; +export const STATUS_TYPES = [ + { value: 'checked', label: __( 'Checked', 'jetpack-components' ) }, + { value: 'unchecked', label: __( 'Unchecked', 'jetpack-components' ) }, + { value: 'threat', label: __( 'Threat', 'jetpack-components' ) }, +]; + export const TYPES = [ { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) }, diff --git a/projects/js-packages/components/components/scan-report/index.tsx b/projects/js-packages/components/components/scan-report/index.tsx index 14795376f7d95..4600ecf98d9db 100644 --- a/projects/js-packages/components/components/scan-report/index.tsx +++ b/projects/js-packages/components/components/scan-report/index.tsx @@ -7,7 +7,7 @@ import { DataViews, filterSortAndPaginate, } from '@wordpress/dataviews'; -import { __ } from '@wordpress/i18n'; +import { __, _n } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; import ShieldIcon from '../shield-icon'; @@ -17,6 +17,7 @@ import { FIELD_ICON, FIELD_STATUS, FIELD_TYPE, + STATUS_TYPES, TYPES, ICONS, } from './constants'; @@ -26,12 +27,13 @@ import styles from './styles.module.scss'; * DataViews component for displaying a scan report. * * @param {object} props - Component props. + * @param {string} props.dataSource - Data source. * @param {Array} props.data - Scan report data. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. * * @return {JSX.Element} The ScanReport component. */ -export default function ScanReport( { data, onChangeSelection } ): JSX.Element { +export default function ScanReport( { dataSource, data, onChangeSelection } ): JSX.Element { const baseView = { search: '', filters: [], @@ -84,8 +86,19 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { const result: Field< ScanReportExtension >[] = [ { id: FIELD_STATUS, + elements: STATUS_TYPES, label: __( 'Status', 'jetpack-components' ), + getValue( { item } ) { + if ( item.checked ) { + if ( item.threats.length > 0 ) { + return 'threat'; + } + return 'checked'; + } + return 'unchecked'; + }, render( { item }: { item: ScanReportExtension } ) { + const scanApi = 'scan_api' === dataSource; let variant: 'info' | 'warning' | 'success' = 'info'; let text = __( 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.', @@ -95,10 +108,34 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { if ( item.checked ) { if ( item.threats.length > 0 ) { variant = 'warning'; - text = __( 'Threat detected.', 'jetpack-components' ); + text = _n( + 'Vulnerability detected.', + 'Vulnerabilities detected.', + item.threats.length, + 'jetpack-components' + ); + + if ( scanApi ) { + text = _n( + 'Threat detected.', + 'Threats detected.', + item.threats.length, + 'jetpack-components' + ); + } } else { variant = 'success'; - text = __( 'No known threats found that affect this version.', 'jetpack-components' ); + text = __( + 'No known vulnerabilities found that affect this version.', + 'jetpack-components' + ); + + if ( scanApi ) { + text = __( + 'No known threats found that affect this version.', + 'jetpack-components' + ); + } } } @@ -127,6 +164,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { { id: FIELD_VERSION, label: __( 'Version', 'jetpack-components' ), + enableSorting: false, enableGlobalSearch: true, getValue( { item }: { item: ScanReportExtension } ) { return item.version ? item.version : ''; @@ -155,7 +193,7 @@ export default function ScanReport( { data, onChangeSelection } ): JSX.Element { ]; return result; - }, [ view ] ); + }, [ view, dataSource ] ); /** * Apply the view settings (i.e. filters, sorting, pagination) to the dataset. diff --git a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx index 63926908850de..eebcbc428fb39 100644 --- a/projects/js-packages/components/components/scan-report/stories/index.stories.tsx +++ b/projects/js-packages/components/components/scan-report/stories/index.stories.tsx @@ -20,6 +20,7 @@ export default { export const Default = args => ; Default.args = { + dataSource: 'scan_api', data: [ { id: 1, @@ -64,6 +65,12 @@ Default.args = { title: 'Malicious code found in file: jptt_eicar.php', severity: 1, }, + { + id: 198352407, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + severity: 1, + }, ], checked: true, type: 'files', diff --git a/projects/js-packages/components/components/threats-data-views/constants.ts b/projects/js-packages/components/components/threats-data-views/constants.ts index cc37c28c7b39b..40941b43ad032 100644 --- a/projects/js-packages/components/components/threats-data-views/constants.ts +++ b/projects/js-packages/components/components/threats-data-views/constants.ts @@ -19,6 +19,7 @@ export const THREAT_TYPES = [ { value: 'themes', label: __( 'Theme', 'jetpack-components' ) }, { value: 'core', label: __( 'WordPress', 'jetpack-components' ) }, { value: 'file', label: __( 'File', 'jetpack-components' ) }, + { value: '', label: __( 'Unknown', 'jetpack-components' ) }, ]; export const THREAT_ICONS = { diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 44efc998dc3ae..27cbfa23935fe 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -265,18 +265,7 @@ export default function ThreatsDataViews( { label: __( 'Type', 'jetpack-components' ), elements: THREAT_TYPES, getValue( { item }: { item: Threat } ) { - switch ( getThreatType( item ) ) { - case 'core': - return __( 'WordPress', 'jetpack-components' ); - case 'plugins': - return __( 'Plugin', 'jetpack-components' ); - case 'themes': - return __( 'Theme', 'jetpack-components' ); - case 'file': - return __( 'File', 'jetpack-components' ); - default: - return __( 'Unknown', 'jetpack-components' ); - } + return getThreatType( item ) ?? ''; }, }, { diff --git a/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home new file mode 100644 index 0000000000000..0478ae51501b8 --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-add-scan-report-to-home @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ScanReport to HomeRoute diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx index 837f649c67f16..c302f93dd8863 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx @@ -88,12 +88,12 @@ const FirewallAdminSectionHero = () => { { heading } - { subheading } + { subheading } { wafSupported && ( diff --git a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx index 1eebd67cb60d7..7b1fd7cbbbede 100644 --- a/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx @@ -1,13 +1,11 @@ -import { Text, useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; +import { useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; import { __, sprintf } from '@wordpress/i18n'; import { Icon, shield, chartBar } from '@wordpress/icons'; import { useCallback, useMemo } from 'react'; -import usePlan from '../../hooks/use-plan'; import useWafData from '../../hooks/use-waf-data'; import styles from './styles.module.scss'; const FirewallStatCards = () => { - const { hasPlan } = usePlan(); const { config: { bruteForceProtection: isBruteForceModuleEnabled }, isEnabled: isWafModuleEnabled, @@ -22,26 +20,22 @@ const FirewallStatCards = () => { const { currentDay: currentDayBlockCount, thirtyDays: thirtyDayBlockCounts } = stats ? stats.blockedRequests : { currentDay: 0, thirtyDays: 0 }; - const isFeatureDisabled = ! isSupportedWafFeatureEnabled || ! hasPlan; const defaultArgs = useMemo( () => ( { - className: isFeatureDisabled ? styles.disabled : styles.active, + className: ! isSupportedWafFeatureEnabled ? styles.disabled : styles.active, variant: isSmall ? 'horizontal' : 'square', } ), - [ isFeatureDisabled, isSmall ] + [ isSupportedWafFeatureEnabled, isSmall ] ); const StatCardIcon = useCallback( ( { icon } ) => ( - { ! isSmall && ! hasPlan && ( - { __( 'Paid feature', 'jetpack-protect' ) } - ) } ), - [ isSmall, hasPlan ] + [] ); const StatCardLabel = useCallback( @@ -77,9 +71,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : currentDayBlockCount, + value: ! isSupportedWafFeatureEnabled ? 0 : currentDayBlockCount, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, currentDayBlockCount ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, currentDayBlockCount ] ); const thirtyDaysArgs = useMemo( @@ -87,9 +81,9 @@ const FirewallStatCards = () => { ...defaultArgs, icon: , label: , - value: isFeatureDisabled ? 0 : thirtyDayBlockCounts, + value: ! isSupportedWafFeatureEnabled ? 0 : thirtyDayBlockCounts, } ), - [ defaultArgs, StatCardIcon, StatCardLabel, isFeatureDisabled, thirtyDayBlockCounts ] + [ defaultArgs, StatCardIcon, StatCardLabel, isSupportedWafFeatureEnabled, thirtyDayBlockCounts ] ); return ( diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx index 12d887e933f43..d695f05eea9cc 100644 --- a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -29,7 +29,8 @@ const HomeAdminSectionHero: React.FC = () => { ) : __( 'We stay ahead of security vulnerabilities to keep your site protected.', - 'jetpack-protect' + 'jetpack-protect', + /* dummy arg to avoid bad minification */ 0 ) }