@automattic/jetpack-components 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/components/diff-viewer/styles.module.scss +1 -1
- package/components/icons/index.tsx +13 -0
- package/components/marked-lines/styles.module.scss +1 -1
- package/components/threat-fixer-button/index.tsx +7 -67
- package/components/threat-modal/fixer-state-notice.tsx +63 -0
- package/components/threat-modal/index.tsx +108 -0
- package/components/threat-modal/styles.module.scss +81 -0
- package/components/threat-modal/threat-actions.tsx +93 -0
- package/components/threat-modal/threat-fix-confirmation.tsx +54 -0
- package/components/threat-modal/threat-fix-details.tsx +62 -0
- package/components/threat-modal/threat-notice.tsx +84 -0
- package/components/threat-modal/threat-summary.tsx +30 -0
- package/components/threat-modal/threat-technical-details.tsx +67 -0
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
### This is a list detailing changes for the Jetpack RNA Components package releases.
|
|
4
4
|
|
|
5
|
+
## [0.63.0] - 2024-11-26
|
|
6
|
+
### Changed
|
|
7
|
+
- Updates ThreatModal flow [#40214]
|
|
8
|
+
|
|
9
|
+
## [0.62.0] - 2024-11-25
|
|
10
|
+
### Added
|
|
11
|
+
- Add Stats icon [#40236]
|
|
12
|
+
- Adds ThreatModal component and stories [#40197]
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Updated package dependencies. [#40288]
|
|
16
|
+
|
|
5
17
|
## [0.61.0] - 2024-11-18
|
|
6
18
|
### Added
|
|
7
19
|
- Gridicon Component: Add support for help-outline icon. [#39867]
|
|
@@ -1218,6 +1230,8 @@
|
|
|
1218
1230
|
### Changed
|
|
1219
1231
|
- Update node version requirement to 14.16.1
|
|
1220
1232
|
|
|
1233
|
+
[0.63.0]: https://github.com/Automattic/jetpack-components/compare/0.62.0...0.63.0
|
|
1234
|
+
[0.62.0]: https://github.com/Automattic/jetpack-components/compare/0.61.0...0.62.0
|
|
1221
1235
|
[0.61.0]: https://github.com/Automattic/jetpack-components/compare/0.60.0...0.61.0
|
|
1222
1236
|
[0.60.0]: https://github.com/Automattic/jetpack-components/compare/0.59.0...0.60.0
|
|
1223
1237
|
[0.59.0]: https://github.com/Automattic/jetpack-components/compare/0.58.1...0.59.0
|
|
@@ -234,6 +234,18 @@ export const AiIcon: React.FC< BaseIconProps > = ( {
|
|
|
234
234
|
);
|
|
235
235
|
};
|
|
236
236
|
|
|
237
|
+
export const StatsIcon: React.FC< BaseIconProps > = ( { opacity = 1, size, color } ) => {
|
|
238
|
+
return (
|
|
239
|
+
<IconWrapper size={ size } opacity={ opacity } color={ color }>
|
|
240
|
+
<Path
|
|
241
|
+
fillRule="evenodd"
|
|
242
|
+
clipRule="evenodd"
|
|
243
|
+
d="M11.25 5H12.75V20H11.25V5ZM6 10H7.5V20H6V10ZM18 14H16.5V20H18V14Z"
|
|
244
|
+
/>
|
|
245
|
+
</IconWrapper>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
237
249
|
const jetpackIcons = {
|
|
238
250
|
'anti-spam': AntiSpamIcon,
|
|
239
251
|
backup: BackupIcon,
|
|
@@ -249,6 +261,7 @@ const jetpackIcons = {
|
|
|
249
261
|
jetpack: JetpackIcon,
|
|
250
262
|
share: ShareIcon,
|
|
251
263
|
ai: AiIcon,
|
|
264
|
+
stats: StatsIcon,
|
|
252
265
|
};
|
|
253
266
|
|
|
254
267
|
const iconsMap = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Button } from '@automattic/jetpack-components';
|
|
2
2
|
import {
|
|
3
3
|
type Threat,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
getFixerState,
|
|
5
|
+
getFixerAction,
|
|
6
|
+
getFixerDescription,
|
|
7
7
|
} from '@automattic/jetpack-scan';
|
|
8
8
|
import { Tooltip } from '@wordpress/components';
|
|
9
9
|
import { useCallback, useMemo } from '@wordpress/element';
|
|
@@ -30,10 +30,7 @@ export default function ThreatFixerButton( {
|
|
|
30
30
|
className?: string;
|
|
31
31
|
} ): JSX.Element {
|
|
32
32
|
const fixerState = useMemo( () => {
|
|
33
|
-
|
|
34
|
-
const error = threat.fixer && fixerIsInError( threat.fixer );
|
|
35
|
-
const stale = threat.fixer && fixerStatusIsStale( threat.fixer );
|
|
36
|
-
return { inProgress, error, stale };
|
|
33
|
+
return getFixerState( threat.fixer );
|
|
37
34
|
}, [ threat.fixer ] );
|
|
38
35
|
|
|
39
36
|
const tooltipText = useMemo( () => {
|
|
@@ -53,54 +50,7 @@ export default function ThreatFixerButton( {
|
|
|
53
50
|
return __( 'An auto-fixer is in progress.', 'jetpack' );
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
case 'delete':
|
|
58
|
-
if ( threat.filename ) {
|
|
59
|
-
if ( threat.filename.endsWith( '/' ) ) {
|
|
60
|
-
return __( 'Deletes the directory that the infected file is in.', 'jetpack' );
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if ( threat.signature === 'Core.File.Modification' ) {
|
|
64
|
-
return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack' );
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return __( 'Deletes the infected file.', 'jetpack' );
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if ( threat.extension?.type === 'plugin' ) {
|
|
71
|
-
return __( 'Deletes the plugin directory to fix the threat.', 'jetpack' );
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if ( threat.extension?.type === 'theme' ) {
|
|
75
|
-
return __( 'Deletes the theme directory to fix the threat.', 'jetpack' );
|
|
76
|
-
}
|
|
77
|
-
break;
|
|
78
|
-
case 'update':
|
|
79
|
-
return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack' );
|
|
80
|
-
case 'replace':
|
|
81
|
-
case 'rollback':
|
|
82
|
-
if ( threat.filename ) {
|
|
83
|
-
return threat.signature === 'Core.File.Modification'
|
|
84
|
-
? __(
|
|
85
|
-
'Replaces the modified core WordPress file with the original clean version from the WordPress source code.',
|
|
86
|
-
'jetpack'
|
|
87
|
-
)
|
|
88
|
-
: __(
|
|
89
|
-
'Replaces the infected file with a previously backed up version that is clean.',
|
|
90
|
-
'jetpack'
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) {
|
|
95
|
-
return __(
|
|
96
|
-
'Replaces the default salt keys in wp-config.php with unique ones.',
|
|
97
|
-
'jetpack'
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
break;
|
|
101
|
-
default:
|
|
102
|
-
return __( 'An auto-fixer is available.', 'jetpack' );
|
|
103
|
-
}
|
|
53
|
+
return getFixerDescription( threat );
|
|
104
54
|
}, [ threat, fixerState ] );
|
|
105
55
|
|
|
106
56
|
const buttonText = useMemo( () => {
|
|
@@ -112,18 +62,8 @@ export default function ThreatFixerButton( {
|
|
|
112
62
|
return __( 'Error', 'jetpack' );
|
|
113
63
|
}
|
|
114
64
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return __( 'Delete', 'jetpack' );
|
|
118
|
-
case 'update':
|
|
119
|
-
return __( 'Update', 'jetpack' );
|
|
120
|
-
case 'replace':
|
|
121
|
-
case 'rollback':
|
|
122
|
-
return __( 'Replace', 'jetpack' );
|
|
123
|
-
default:
|
|
124
|
-
return __( 'Fix', 'jetpack' );
|
|
125
|
-
}
|
|
126
|
-
}, [ threat.fixable, fixerState.error ] );
|
|
65
|
+
return getFixerAction( threat );
|
|
66
|
+
}, [ threat, fixerState.error ] );
|
|
127
67
|
|
|
128
68
|
const handleClick = useCallback(
|
|
129
69
|
( event: React.MouseEvent ) => {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import styles from './styles.module.scss';
|
|
4
|
+
import ThreatNotice from './threat-notice';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FixerStateNotice component
|
|
8
|
+
*
|
|
9
|
+
* @param {object} props - The component props.
|
|
10
|
+
* @param {object} props.fixerState - The state of the fixer (inProgress, error, stale).
|
|
11
|
+
* @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress.
|
|
12
|
+
* @param {boolean} props.fixerState.error - Whether the fixer encountered an error.
|
|
13
|
+
* @param {boolean} props.fixerState.stale - Whether the fixer is stale.
|
|
14
|
+
*
|
|
15
|
+
* @return {JSX.Element | null} The rendered fixer notice or null if no notice is available.
|
|
16
|
+
*/
|
|
17
|
+
const FixerStateNotice = ( {
|
|
18
|
+
fixerState,
|
|
19
|
+
}: {
|
|
20
|
+
fixerState: { inProgress: boolean; error: boolean; stale: boolean };
|
|
21
|
+
} ) => {
|
|
22
|
+
const { status, title, content } = useMemo( () => {
|
|
23
|
+
if ( fixerState.error ) {
|
|
24
|
+
return {
|
|
25
|
+
status: 'error' as const,
|
|
26
|
+
title: __( 'An error occurred auto-fixing this threat', 'jetpack' ),
|
|
27
|
+
content: __(
|
|
28
|
+
'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.',
|
|
29
|
+
'jetpack'
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ( fixerState.stale ) {
|
|
35
|
+
return {
|
|
36
|
+
status: 'error' as const,
|
|
37
|
+
title: __( 'The auto-fixer is taking longer than expected', 'jetpack' ),
|
|
38
|
+
content: __(
|
|
39
|
+
'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.',
|
|
40
|
+
'jetpack'
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if ( fixerState.inProgress ) {
|
|
46
|
+
return {
|
|
47
|
+
status: 'success' as const,
|
|
48
|
+
title: __( 'An auto-fixer is in progress', 'jetpack' ),
|
|
49
|
+
content: __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {};
|
|
54
|
+
}, [ fixerState ] );
|
|
55
|
+
|
|
56
|
+
return title ? (
|
|
57
|
+
<div className={ styles[ 'fixer-notice' ] }>
|
|
58
|
+
<ThreatNotice status={ status } title={ title } content={ content } />
|
|
59
|
+
</div>
|
|
60
|
+
) : null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default FixerStateNotice;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type Threat } from '@automattic/jetpack-scan';
|
|
2
|
+
import { Modal } from '@wordpress/components';
|
|
3
|
+
import { createContext } from 'react';
|
|
4
|
+
import Text from '../text';
|
|
5
|
+
import ThreatSeverityBadge from '../threat-severity-badge';
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
import ThreatFixConfirmation from './threat-fix-confirmation';
|
|
8
|
+
interface ThreatModalContextType {
|
|
9
|
+
closeModal: () => void;
|
|
10
|
+
threat: Threat;
|
|
11
|
+
handleUpgradeClick?: () => void;
|
|
12
|
+
userConnectionNeeded: boolean;
|
|
13
|
+
handleConnectUser: () => void;
|
|
14
|
+
userIsConnecting: boolean;
|
|
15
|
+
siteCredentialsNeeded: boolean;
|
|
16
|
+
credentialsIsFetching: boolean;
|
|
17
|
+
credentialsRedirectUrl: string;
|
|
18
|
+
handleFixThreatClick?: ( threats: Threat[] ) => void;
|
|
19
|
+
handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
|
|
20
|
+
handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ThreatModalContext = createContext< ThreatModalContextType | null >( null );
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ThreatModal component
|
|
27
|
+
*
|
|
28
|
+
* @param {object} props - The props.
|
|
29
|
+
* @param {object} props.threat - The threat.
|
|
30
|
+
* @param {boolean} props.isUserConnected - Whether the user is connected.
|
|
31
|
+
* @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner.
|
|
32
|
+
* @param {boolean} props.userIsConnecting - Whether the user is connecting.
|
|
33
|
+
* @param {Function} props.handleConnectUser - The handleConnectUser function.
|
|
34
|
+
* @param {object} props.credentials - The credentials.
|
|
35
|
+
* @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching.
|
|
36
|
+
* @param {string} props.credentialsRedirectUrl - The credentials redirect URL.
|
|
37
|
+
* @param {Function} props.handleUpgradeClick - The handleUpgradeClick function.
|
|
38
|
+
* @param {Function} props.handleFixThreatClick - The handleFixThreatClick function.
|
|
39
|
+
* @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function.
|
|
40
|
+
* @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function.
|
|
41
|
+
*
|
|
42
|
+
* @return {JSX.Element} The threat modal.
|
|
43
|
+
*/
|
|
44
|
+
export default function ThreatModal( {
|
|
45
|
+
threat,
|
|
46
|
+
isUserConnected,
|
|
47
|
+
hasConnectedOwner,
|
|
48
|
+
userIsConnecting,
|
|
49
|
+
handleConnectUser,
|
|
50
|
+
credentials,
|
|
51
|
+
credentialsIsFetching,
|
|
52
|
+
credentialsRedirectUrl,
|
|
53
|
+
handleUpgradeClick,
|
|
54
|
+
handleFixThreatClick,
|
|
55
|
+
handleIgnoreThreatClick,
|
|
56
|
+
handleUnignoreThreatClick,
|
|
57
|
+
...modalProps
|
|
58
|
+
}: {
|
|
59
|
+
threat: Threat;
|
|
60
|
+
isUserConnected: boolean;
|
|
61
|
+
hasConnectedOwner: boolean;
|
|
62
|
+
userIsConnecting: boolean;
|
|
63
|
+
handleConnectUser: () => void;
|
|
64
|
+
credentials: false | Record< string, unknown >[];
|
|
65
|
+
credentialsIsFetching: boolean;
|
|
66
|
+
credentialsRedirectUrl: string;
|
|
67
|
+
handleUpgradeClick?: () => void;
|
|
68
|
+
handleFixThreatClick?: ( threats: Threat[] ) => void;
|
|
69
|
+
handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
|
|
70
|
+
handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
|
|
71
|
+
} & React.ComponentProps< typeof Modal > ): JSX.Element {
|
|
72
|
+
const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner;
|
|
73
|
+
const siteCredentialsNeeded = ! credentials || credentials.length === 0;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Modal
|
|
77
|
+
title={
|
|
78
|
+
<div className={ styles.title }>
|
|
79
|
+
<Text variant="title-small">{ threat.title }</Text>
|
|
80
|
+
{ !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
|
|
81
|
+
</div>
|
|
82
|
+
}
|
|
83
|
+
size="large"
|
|
84
|
+
{ ...modalProps }
|
|
85
|
+
>
|
|
86
|
+
<div className={ styles[ 'threat-details' ] }>
|
|
87
|
+
<ThreatModalContext.Provider
|
|
88
|
+
value={ {
|
|
89
|
+
closeModal: modalProps.onRequestClose,
|
|
90
|
+
threat,
|
|
91
|
+
handleUpgradeClick,
|
|
92
|
+
userConnectionNeeded,
|
|
93
|
+
handleConnectUser,
|
|
94
|
+
userIsConnecting,
|
|
95
|
+
siteCredentialsNeeded,
|
|
96
|
+
credentialsIsFetching,
|
|
97
|
+
credentialsRedirectUrl,
|
|
98
|
+
handleFixThreatClick,
|
|
99
|
+
handleIgnoreThreatClick,
|
|
100
|
+
handleUnignoreThreatClick,
|
|
101
|
+
} }
|
|
102
|
+
>
|
|
103
|
+
<ThreatFixConfirmation />
|
|
104
|
+
</ThreatModalContext.Provider>
|
|
105
|
+
</div>
|
|
106
|
+
</Modal>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
.threat-details {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: calc( var( --spacing-base ) * 3 ); // 24px
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.section {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: calc( var( --spacing-base ) * 2 ); // 16px
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.section .section__toggle {
|
|
14
|
+
text-decoration: none;
|
|
15
|
+
|
|
16
|
+
&:hover {
|
|
17
|
+
text-decoration: underline;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__content {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: calc( var( --spacing-base ) / 2 ); // 4px
|
|
23
|
+
align-items: center;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.title {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: calc( var( --spacing-base ) * 1.5 ); // 12px
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.filename {
|
|
34
|
+
background-color: var( --jp-gray-0 );
|
|
35
|
+
padding: calc( var( --spacing-base ) * 3 ); // 24px
|
|
36
|
+
overflow-x: auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.modal-footer {
|
|
40
|
+
padding-top: calc( var( --spacing-base ) * 3 ); // 24px
|
|
41
|
+
border-top: 1px solid var( --jp-gray-5 );
|
|
42
|
+
|
|
43
|
+
.threat-actions {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
gap: calc( var( --spacing-base ) * 2 ); // 16px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fixer-notice {
|
|
51
|
+
padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.notice {
|
|
55
|
+
&__title {
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: calc( var( --spacing-base ) / 2 ); // 4px
|
|
58
|
+
|
|
59
|
+
p {
|
|
60
|
+
font-weight: bold;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__actions {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: calc( var( --spacing-base ) * 2 ); // 16px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__action {
|
|
70
|
+
margin-top: calc( var( --spacing-base ) * 2 ); // 16px;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
svg.spinner {
|
|
75
|
+
color: var( --jp-black );
|
|
76
|
+
height: 20px;
|
|
77
|
+
width: 20px;
|
|
78
|
+
margin-left: calc( var( --spacing-base ) / 2 ); // 4px;
|
|
79
|
+
margin-right: 6px;
|
|
80
|
+
|
|
81
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Button } from '@automattic/jetpack-components';
|
|
2
|
+
import { getFixerState, getDetailedFixerAction } from '@automattic/jetpack-scan';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
import { useCallback, useContext, useMemo } from 'react';
|
|
5
|
+
import FixerStateNotice from './fixer-state-notice';
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
import { ThreatModalContext } from '.';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ThreatActions component
|
|
11
|
+
*
|
|
12
|
+
* @return {JSX.Element | null} The rendered action buttons or null if no actions are available.
|
|
13
|
+
*/
|
|
14
|
+
const ThreatActions = (): JSX.Element => {
|
|
15
|
+
const {
|
|
16
|
+
closeModal,
|
|
17
|
+
threat,
|
|
18
|
+
handleFixThreatClick,
|
|
19
|
+
handleIgnoreThreatClick,
|
|
20
|
+
handleUnignoreThreatClick,
|
|
21
|
+
userConnectionNeeded,
|
|
22
|
+
siteCredentialsNeeded,
|
|
23
|
+
} = useContext( ThreatModalContext );
|
|
24
|
+
const disabled = userConnectionNeeded || siteCredentialsNeeded;
|
|
25
|
+
|
|
26
|
+
const fixerState = useMemo( () => {
|
|
27
|
+
return getFixerState( threat.fixer );
|
|
28
|
+
}, [ threat.fixer ] );
|
|
29
|
+
|
|
30
|
+
const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] );
|
|
31
|
+
|
|
32
|
+
const onFixClick = useCallback( () => {
|
|
33
|
+
handleFixThreatClick?.( [ threat ] );
|
|
34
|
+
closeModal();
|
|
35
|
+
}, [ threat, handleFixThreatClick, closeModal ] );
|
|
36
|
+
|
|
37
|
+
const onIgnoreClick = useCallback( () => {
|
|
38
|
+
handleIgnoreThreatClick?.( [ threat ] );
|
|
39
|
+
closeModal();
|
|
40
|
+
}, [ threat, handleIgnoreThreatClick, closeModal ] );
|
|
41
|
+
|
|
42
|
+
const onUnignoreClick = useCallback( () => {
|
|
43
|
+
handleUnignoreThreatClick?.( [ threat ] );
|
|
44
|
+
closeModal();
|
|
45
|
+
}, [ threat, handleUnignoreThreatClick, closeModal ] );
|
|
46
|
+
|
|
47
|
+
if ( ! threat?.status || threat.status === 'fixed' ) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={ styles[ 'modal-footer' ] }>
|
|
53
|
+
<FixerStateNotice fixerState={ fixerState } />
|
|
54
|
+
<div className={ styles[ 'threat-actions' ] }>
|
|
55
|
+
{ threat.status === 'ignored' && (
|
|
56
|
+
<Button
|
|
57
|
+
disabled={ disabled }
|
|
58
|
+
isDestructive={ true }
|
|
59
|
+
variant="secondary"
|
|
60
|
+
onClick={ onUnignoreClick }
|
|
61
|
+
>
|
|
62
|
+
{ __( 'Un-ignore threat', 'jetpack' ) }
|
|
63
|
+
</Button>
|
|
64
|
+
) }
|
|
65
|
+
{ threat.status === 'current' && (
|
|
66
|
+
<>
|
|
67
|
+
<Button
|
|
68
|
+
isDestructive={ true }
|
|
69
|
+
variant="secondary"
|
|
70
|
+
onClick={ onIgnoreClick }
|
|
71
|
+
disabled={ disabled || ( fixerState.inProgress && ! fixerState.stale ) }
|
|
72
|
+
>
|
|
73
|
+
{ __( 'Ignore threat', 'jetpack' ) }
|
|
74
|
+
</Button>
|
|
75
|
+
{ threat.fixable && (
|
|
76
|
+
<Button
|
|
77
|
+
isPrimary
|
|
78
|
+
disabled={ disabled || ( fixerState.inProgress && ! fixerState.stale ) }
|
|
79
|
+
onClick={ onFixClick }
|
|
80
|
+
>
|
|
81
|
+
{ fixerState.error || fixerState.stale
|
|
82
|
+
? __( 'Retry fixer', 'jetpack' )
|
|
83
|
+
: detailedFixerAction }
|
|
84
|
+
</Button>
|
|
85
|
+
) }
|
|
86
|
+
</>
|
|
87
|
+
) }
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default ThreatActions;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { __ } from '@wordpress/i18n';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import ThreatActions from './threat-actions';
|
|
4
|
+
import ThreatFixDetails from './threat-fix-details';
|
|
5
|
+
import ThreatNotice from './threat-notice';
|
|
6
|
+
import ThreatSummary from './threat-summary';
|
|
7
|
+
import ThreatTechnicalDetails from './threat-technical-details';
|
|
8
|
+
import { ThreatModalContext } from '.';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ThreatFixConfirmation component
|
|
12
|
+
*
|
|
13
|
+
* @return {JSX.Element} The rendered fix confirmation.
|
|
14
|
+
*/
|
|
15
|
+
const ThreatFixConfirmation = () => {
|
|
16
|
+
const { userConnectionNeeded, siteCredentialsNeeded } = useContext( ThreatModalContext );
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<ThreatSummary />
|
|
20
|
+
<ThreatTechnicalDetails />
|
|
21
|
+
<ThreatFixDetails />
|
|
22
|
+
{ siteCredentialsNeeded && userConnectionNeeded && (
|
|
23
|
+
<ThreatNotice
|
|
24
|
+
title={ 'Additional connections needed' }
|
|
25
|
+
content={ __(
|
|
26
|
+
'A user connection and server credentials provide Jetpack the access necessary to ignore and auto-fix threats on your site.',
|
|
27
|
+
'jetpack'
|
|
28
|
+
) }
|
|
29
|
+
/>
|
|
30
|
+
) }
|
|
31
|
+
{ ! siteCredentialsNeeded && userConnectionNeeded && (
|
|
32
|
+
<ThreatNotice
|
|
33
|
+
title={ __( 'User connection needed', 'jetpack' ) }
|
|
34
|
+
content={ __(
|
|
35
|
+
'A user connection provides Jetpack the access necessary to ignore and auto-fix threats on your site.',
|
|
36
|
+
'jetpack'
|
|
37
|
+
) }
|
|
38
|
+
/>
|
|
39
|
+
) }
|
|
40
|
+
{ siteCredentialsNeeded && ! userConnectionNeeded && (
|
|
41
|
+
<ThreatNotice
|
|
42
|
+
title={ __( 'Site credentials needed', 'jetpack' ) }
|
|
43
|
+
content={ __(
|
|
44
|
+
'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.',
|
|
45
|
+
'jetpack'
|
|
46
|
+
) }
|
|
47
|
+
/>
|
|
48
|
+
) }
|
|
49
|
+
<ThreatActions />
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default ThreatFixConfirmation;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getFixerDescription } from '@automattic/jetpack-scan';
|
|
2
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
3
|
+
import React, { useMemo, useContext } from 'react';
|
|
4
|
+
import ContextualUpgradeTrigger from '../contextual-upgrade-trigger';
|
|
5
|
+
import Text from '../text';
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
import { ThreatModalContext } from '.';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ThreatFixDetails component
|
|
11
|
+
*
|
|
12
|
+
* @return {JSX.Element | null} The rendered fix details or null if no fixable details are available.
|
|
13
|
+
*/
|
|
14
|
+
const ThreatFixDetails = (): JSX.Element => {
|
|
15
|
+
const { threat, handleUpgradeClick } = useContext( ThreatModalContext );
|
|
16
|
+
|
|
17
|
+
const title = useMemo( () => {
|
|
18
|
+
if ( threat.status === 'fixed' ) {
|
|
19
|
+
return __( 'How did Jetpack fix it?', 'jetpack' );
|
|
20
|
+
}
|
|
21
|
+
if ( threat.status === 'current' && threat.fixable ) {
|
|
22
|
+
return __( 'How can Jetpack auto-fix this threat?', 'jetpack' );
|
|
23
|
+
}
|
|
24
|
+
return __( 'How to fix it?', 'jetpack' );
|
|
25
|
+
}, [ threat ] );
|
|
26
|
+
|
|
27
|
+
const fix = useMemo( () => {
|
|
28
|
+
// The threat has a fixed version available, but no auto-fix is available.
|
|
29
|
+
// The user needs to update the extension to the fixed version.
|
|
30
|
+
if ( ! threat.fixable && threat.fixedIn ) {
|
|
31
|
+
return sprintf(
|
|
32
|
+
/* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */
|
|
33
|
+
__( 'Update %1$s to version %2$s.', 'jetpack' ),
|
|
34
|
+
threat.extension.name,
|
|
35
|
+
threat.fixedIn
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The threat has an auto-fix available.
|
|
40
|
+
return getFixerDescription( threat );
|
|
41
|
+
}, [ threat ] );
|
|
42
|
+
|
|
43
|
+
if ( ! threat.fixable && ! threat.fixedIn ) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={ styles.section }>
|
|
49
|
+
<Text variant="title-small">{ title }</Text>
|
|
50
|
+
<Text>{ fix }</Text>
|
|
51
|
+
{ handleUpgradeClick && (
|
|
52
|
+
<ContextualUpgradeTrigger
|
|
53
|
+
description={ __( 'Looking for advanced scan results and one-click fixes?', 'jetpack' ) }
|
|
54
|
+
cta={ __( 'Upgrade Jetpack now', 'jetpack' ) }
|
|
55
|
+
onClick={ handleUpgradeClick }
|
|
56
|
+
/>
|
|
57
|
+
) }
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default ThreatFixDetails;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Text, Button } from '@automattic/jetpack-components';
|
|
2
|
+
import { Notice, Spinner } from '@wordpress/components';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
import { Icon, warning } from '@wordpress/icons';
|
|
5
|
+
import { useContext } from 'react';
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
import { ThreatModalContext } from '.';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ThreatNotice component
|
|
11
|
+
*
|
|
12
|
+
* @param {object} props - The component props.
|
|
13
|
+
* @param {string} props.status - The status of the notice.
|
|
14
|
+
* @param {string} props.title - The title of the notice.
|
|
15
|
+
* @param {string} props.content - The content of the notice.
|
|
16
|
+
*
|
|
17
|
+
* @return {JSX.Element} The rendered ThreatNotice component.
|
|
18
|
+
*/
|
|
19
|
+
const ThreatNotice = ( {
|
|
20
|
+
status = 'warning',
|
|
21
|
+
title,
|
|
22
|
+
content,
|
|
23
|
+
}: {
|
|
24
|
+
status?: 'warning' | 'error' | 'success' | undefined;
|
|
25
|
+
title: string;
|
|
26
|
+
content: string;
|
|
27
|
+
} ): JSX.Element => {
|
|
28
|
+
const {
|
|
29
|
+
userConnectionNeeded,
|
|
30
|
+
userIsConnecting,
|
|
31
|
+
handleConnectUser,
|
|
32
|
+
siteCredentialsNeeded,
|
|
33
|
+
credentialsRedirectUrl,
|
|
34
|
+
credentialsIsFetching,
|
|
35
|
+
} = useContext( ThreatModalContext );
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Notice
|
|
39
|
+
status={ status }
|
|
40
|
+
isDismissible={ false }
|
|
41
|
+
children={
|
|
42
|
+
<div className={ styles.notice }>
|
|
43
|
+
<div className={ styles.notice__title }>
|
|
44
|
+
{ status === 'success' ? (
|
|
45
|
+
<Spinner className={ styles.spinner } />
|
|
46
|
+
) : (
|
|
47
|
+
<Icon icon={ warning } size={ 30 } />
|
|
48
|
+
) }
|
|
49
|
+
<Text variant="title-small" mb={ 2 }>
|
|
50
|
+
{ title }
|
|
51
|
+
</Text>
|
|
52
|
+
</div>
|
|
53
|
+
<Text>{ content }</Text>
|
|
54
|
+
<div className={ styles.notice__actions }>
|
|
55
|
+
{ userConnectionNeeded && (
|
|
56
|
+
<Button
|
|
57
|
+
className={ styles.notice__action }
|
|
58
|
+
isExternalLink={ true }
|
|
59
|
+
weight="regular"
|
|
60
|
+
isLoading={ userIsConnecting }
|
|
61
|
+
onClick={ handleConnectUser }
|
|
62
|
+
>
|
|
63
|
+
{ __( 'Connect your user account', 'jetpack' ) }
|
|
64
|
+
</Button>
|
|
65
|
+
) }
|
|
66
|
+
{ siteCredentialsNeeded && (
|
|
67
|
+
<Button
|
|
68
|
+
className={ styles.notice__action }
|
|
69
|
+
isExternalLink={ true }
|
|
70
|
+
weight="regular"
|
|
71
|
+
href={ credentialsRedirectUrl }
|
|
72
|
+
isLoading={ credentialsIsFetching }
|
|
73
|
+
>
|
|
74
|
+
{ __( 'Enter server credentials', 'jetpack' ) }
|
|
75
|
+
</Button>
|
|
76
|
+
) }
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default ThreatNotice;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Button } from '@automattic/jetpack-components';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import Text from '../text';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
import { ThreatModalContext } from '.';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ThreatSummary component
|
|
10
|
+
*
|
|
11
|
+
* @return {JSX.Element} The rendered threat summary.
|
|
12
|
+
*/
|
|
13
|
+
const ThreatSummary = (): JSX.Element => {
|
|
14
|
+
const { threat } = useContext( ThreatModalContext );
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className={ styles.section }>
|
|
18
|
+
{ !! threat.description && <Text>{ threat.description }</Text> }
|
|
19
|
+
{ !! threat.source && (
|
|
20
|
+
<div>
|
|
21
|
+
<Button variant="link" isExternalLink={ true } weight="regular" href={ threat.source }>
|
|
22
|
+
{ __( 'See more technical details of this threat', 'jetpack' ) }
|
|
23
|
+
</Button>
|
|
24
|
+
</div>
|
|
25
|
+
) }
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default ThreatSummary;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Text, Button } from '@automattic/jetpack-components';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import { chevronDown, chevronUp, Icon } from '@wordpress/icons';
|
|
4
|
+
import { useState, useCallback, useContext } from 'react';
|
|
5
|
+
import DiffViewer from '../diff-viewer';
|
|
6
|
+
import MarkedLines from '../marked-lines';
|
|
7
|
+
import styles from './styles.module.scss';
|
|
8
|
+
import { ThreatModalContext } from '.';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ThreatTechnicalDetails component
|
|
12
|
+
*
|
|
13
|
+
* @return {JSX.Element | null} The rendered technical details or null if no details are available.
|
|
14
|
+
*/
|
|
15
|
+
const ThreatTechnicalDetails = (): JSX.Element => {
|
|
16
|
+
const { threat } = useContext( ThreatModalContext );
|
|
17
|
+
|
|
18
|
+
const [ open, setOpen ] = useState( false );
|
|
19
|
+
|
|
20
|
+
const toggleOpen = useCallback( () => {
|
|
21
|
+
setOpen( ! open );
|
|
22
|
+
}, [ open ] );
|
|
23
|
+
|
|
24
|
+
if ( ! threat.filename && ! threat.context && ! threat.diff ) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={ styles.section }>
|
|
30
|
+
<div className={ styles.section__title }>
|
|
31
|
+
<Button
|
|
32
|
+
variant="link"
|
|
33
|
+
className={ styles.section__toggle }
|
|
34
|
+
aria-expanded={ open }
|
|
35
|
+
aria-controls={ `threat-details-${ threat.id }` }
|
|
36
|
+
onClick={ toggleOpen }
|
|
37
|
+
>
|
|
38
|
+
<div className={ styles.section__toggle__content }>
|
|
39
|
+
<Text variant="title-small" mb={ 0 }>
|
|
40
|
+
{ open
|
|
41
|
+
? __( 'Hide the technical details', 'jetpack' )
|
|
42
|
+
: __( 'Show the technical details', 'jetpack' ) }
|
|
43
|
+
</Text>
|
|
44
|
+
<Icon icon={ open ? chevronUp : chevronDown } size={ 24 } />
|
|
45
|
+
</div>
|
|
46
|
+
</Button>
|
|
47
|
+
</div>
|
|
48
|
+
{ open && (
|
|
49
|
+
<div
|
|
50
|
+
className={ open ? styles.section__open : styles.section__closed }
|
|
51
|
+
id={ `threat-details-${ threat.id }` }
|
|
52
|
+
>
|
|
53
|
+
{ threat.filename && (
|
|
54
|
+
<>
|
|
55
|
+
<Text>{ __( 'Threat found in file:', 'jetpack' ) }</Text>
|
|
56
|
+
<pre className={ styles.filename }>{ threat.filename }</pre>
|
|
57
|
+
</>
|
|
58
|
+
) }
|
|
59
|
+
{ threat.context && <MarkedLines context={ threat.context } /> }
|
|
60
|
+
{ threat.diff && <DiffViewer diff={ threat.diff } /> }
|
|
61
|
+
</div>
|
|
62
|
+
) }
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default ThreatTechnicalDetails;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"description": "Jetpack Components Package",
|
|
5
5
|
"author": "Automattic",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -15,29 +15,29 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@automattic/format-currency": "1.0.1",
|
|
18
|
-
"@automattic/jetpack-boost-score-api": "^0.1.
|
|
19
|
-
"@automattic/jetpack-scan": "^0.
|
|
18
|
+
"@automattic/jetpack-boost-score-api": "^0.1.47",
|
|
19
|
+
"@automattic/jetpack-scan": "^0.4.0",
|
|
20
20
|
"@babel/runtime": "^7",
|
|
21
|
-
"@wordpress/browserslist-config": "6.
|
|
22
|
-
"@wordpress/components": "28.
|
|
23
|
-
"@wordpress/compose": "7.
|
|
24
|
-
"@wordpress/data": "10.
|
|
25
|
-
"@wordpress/dataviews": "4.
|
|
26
|
-
"@wordpress/date": "5.
|
|
27
|
-
"@wordpress/element": "6.
|
|
28
|
-
"@wordpress/i18n": "5.
|
|
29
|
-
"@wordpress/icons": "10.
|
|
30
|
-
"@wordpress/notices": "5.
|
|
21
|
+
"@wordpress/browserslist-config": "6.12.0",
|
|
22
|
+
"@wordpress/components": "28.12.0",
|
|
23
|
+
"@wordpress/compose": "7.12.0",
|
|
24
|
+
"@wordpress/data": "10.12.0",
|
|
25
|
+
"@wordpress/dataviews": "4.8.0",
|
|
26
|
+
"@wordpress/date": "5.12.0",
|
|
27
|
+
"@wordpress/element": "6.12.0",
|
|
28
|
+
"@wordpress/i18n": "5.12.0",
|
|
29
|
+
"@wordpress/icons": "10.12.0",
|
|
30
|
+
"@wordpress/notices": "5.12.0",
|
|
31
31
|
"clsx": "2.1.1",
|
|
32
32
|
"prop-types": "^15.7.2",
|
|
33
33
|
"qrcode.react": "3.1.0",
|
|
34
34
|
"react-slider": "2.0.5",
|
|
35
|
-
"social-logos": "^3.1.
|
|
35
|
+
"social-logos": "^3.1.13",
|
|
36
36
|
"uplot": "1.6.31",
|
|
37
37
|
"uplot-react": "1.1.4"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
40
|
+
"@automattic/jetpack-base-styles": "^0.6.37",
|
|
41
41
|
"@babel/core": "7.26.0",
|
|
42
42
|
"@babel/preset-react": "7.25.9",
|
|
43
43
|
"@jest/globals": "29.4.3",
|