@redsift/products 12.5.9 → 12.5.10-muiv8-alpha.2
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.
|
@@ -5,7 +5,7 @@ import styled, { css } from 'styled-components';
|
|
|
5
5
|
import pako from 'pako';
|
|
6
6
|
import { interpolateTranslation, mapStatusToDetailedCardColor, isStructuredData, calculateSankeyHeight, create2LevelSankey, extractDmarcData, extractSpfData, extractTlsData, extractFcrdnsData, extractDnssecData, extractThreatIntelligenceData, extractUrlsData, extractBimiData, extractAmpData, extractSubdoData, extractDomainAnalyzerDkimData, extractDomainAnalyzerSpfData, extractDmarcDomainData, extractSpfDomainData, extractMtaStsFullData, extractDkimData, recordStatus } from '@redsift/signal-logic';
|
|
7
7
|
import { useLocalizedStringFormatter, useTheme, Button, Icon, DetailedCard, ButtonLink, Flexbox } from '@redsift/design-system';
|
|
8
|
-
import { mdiHelpCircle, mdiClose, mdiAlert, mdiEmail, mdiEarth, mdiInformationOutline, mdiCheck, mdiInformation } from '@redsift/icons';
|
|
8
|
+
import { mdiHelpCircle, mdiClose, mdiAlert, mdiEmail, mdiEarth, mdiInformationOutline, mdiCheck, mdiInformation, mdiFormatAlignRight, mdiFormatAlignJustify } from '@redsift/icons';
|
|
9
9
|
import { createPortal } from 'react-dom';
|
|
10
10
|
|
|
11
11
|
const COLOR = {
|
|
@@ -283,6 +283,7 @@ var EXTRACTOR_STRINGS = {
|
|
|
283
283
|
"card.dkim.unavailable-try-later_md": "Please try sending another email later.",
|
|
284
284
|
"card.dkim.unavailable_md": "**[](DKIM#:glossary) evaluation temporarily unavailable.**",
|
|
285
285
|
"card.dmarc.domain-aligned": "The domain is aligned with the From Domain. It can contribute to passing DMARC.",
|
|
286
|
+
"card.dmarc.domain-aligned-not-verified": "The domain is aligned with the From Domain, but its signature did not verify, so it cannot contribute to passing DMARC.",
|
|
286
287
|
"card.dmarc.domain-not-aligned": "The domain is not aligned with the From Domain. It cannot contribute to passing DMARC.",
|
|
287
288
|
"card.dmarc.error.dns-timeout": "DNS timeout when looking up DMARC record",
|
|
288
289
|
"card.dmarc.error.invalid-pct-value": "Pct value must be between 0 and 100",
|
|
@@ -766,6 +767,7 @@ var EXTRACTOR_STRINGS = {
|
|
|
766
767
|
"card.dkim.unavailable-try-later_md": "Veuillez essayer d'envoyer un autre courriel plus tard.",
|
|
767
768
|
"card.dkim.unavailable_md": "**[](DKIM#:glossary) \xE9valuation temporairement indisponible.**",
|
|
768
769
|
"card.dmarc.domain-aligned": "Le domaine est align\xE9 avec le domaine exp\xE9diteur. Il peut contribuer \xE0 la r\xE9ussite de DMARC.",
|
|
770
|
+
"card.dmarc.domain-aligned-not-verified": "Le domaine est align\xE9 avec le domaine exp\xE9diteur, mais sa signature n'a pas \xE9t\xE9 v\xE9rifi\xE9e, il ne peut donc pas contribuer \xE0 la r\xE9ussite de DMARC.",
|
|
769
771
|
"card.dmarc.domain-not-aligned": "Le domaine n'est pas align\xE9 avec le domaine exp\xE9diteur. Il ne peut pas contribuer \xE0 la r\xE9ussite de DMARC.",
|
|
770
772
|
"card.dmarc.error.dns-timeout": "D\xE9lai d'attente DNS lors de la recherche de l'enregistrement DMARC",
|
|
771
773
|
"card.dmarc.error.invalid-pct-value": "La valeur pct doit \xEAtre entre 0 et 100",
|
|
@@ -1334,7 +1336,7 @@ var enUS = {
|
|
|
1334
1336
|
"investigate.card.dmarc.void-dynamic-dmarc-ns-record": "Dynamic DMARC record (NS) is added but Dynamic Services aren't enabled",
|
|
1335
1337
|
"investigate.card.dmarc.policy-none-pct-0_md": "No reports will be sent with this policy, because the sample rate [](PCT#:glossary) is set to `0`.",
|
|
1336
1338
|
"investigate.card.dmarc.policy-not-none-pct-0_md": "All **unauthorized** email will be delivered to receivers although the policy is set to [{policy}](DMARC_POLICY#:glossary), because the sample rate [](PCT#:glossary) is set to `0`.",
|
|
1337
|
-
"investigate.card.dmarc.policy-inherited-from-org-domain_md": "The DMARC policy for the [](FROM#:glossary) `{fromDomain}` comes from the [](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
|
|
1339
|
+
"investigate.card.dmarc.policy-inherited-from-org-domain_md": "The DMARC policy for the [](FROM#:glossary) `{fromDomain}` comes from the [Organizational Domain](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
|
|
1338
1340
|
"investigate.card.dmarc.category-policy-good_md": "The policy on your DNS conforms with the DMARC specification.",
|
|
1339
1341
|
"investigate.card.dmarc.all-emails-reported_md": "All emails will be [REPORTED](DMARC_POLICY#:glossary).",
|
|
1340
1342
|
"investigate.card.dmarc.all-emails-reported-only_md": "DMARC configured in report-only mode. All emails will only be [REPORTED](DMARC_POLICY#:glossary).",
|
|
@@ -1924,6 +1926,7 @@ var enUS = {
|
|
|
1924
1926
|
"investigate.section-titles.bimi-domain": "DMARC Domains",
|
|
1925
1927
|
"investigate.section-titles.dmarc-domain": "DMARC Domains",
|
|
1926
1928
|
"investigate.card.dmarc.domain-aligned": "The domain is aligned with the From Domain. It can contribute to passing DMARC.",
|
|
1929
|
+
"investigate.card.dmarc.domain-aligned-not-verified": "The domain is aligned with the From Domain, but its signature did not verify, so it cannot contribute to passing DMARC.",
|
|
1927
1930
|
"investigate.card.dmarc.domain-not-aligned": "The domain is not aligned with the From Domain. It cannot contribute to passing DMARC."
|
|
1928
1931
|
};
|
|
1929
1932
|
|
|
@@ -2022,11 +2025,11 @@ var frFR = {
|
|
|
2022
2025
|
"investigate.card.dmarc.policy-remaining-emails-pct-reject_md": "Les **{remainingPercent}%** restants de courriels non autorisés seront soumis à la politique de **mise en quarantaine**.",
|
|
2023
2026
|
"investigate.card.dmarc.policy-remaining-emails-pct-quarantine_md": "Les **{remainingPercent}%** restants de courriels non autorisés seront **livrés normalement**.",
|
|
2024
2027
|
"investigate.card.dmarc.no-policy_md": "**Aucune [FULL](DMARC_POLICY#:glossary) publiée sur le domaine**\n\n**`{policyDomain}`**",
|
|
2025
|
-
"investigate.card.dmarc.dmarc-passed_md": "[](DMARC#:glossary) **
|
|
2026
|
-
"investigate.card.dmarc.dmarc-passed-no-rua_md": "[](DMARC#:glossary) est conforme
|
|
2027
|
-
"investigate.card.dmarc.dmarc-no-rua_md": "[](DMARC#:glossary) n'a pas d'
|
|
2028
|
+
"investigate.card.dmarc.dmarc-passed_md": "[](DMARC#:glossary) **réussi**",
|
|
2029
|
+
"investigate.card.dmarc.dmarc-passed-no-rua_md": "[](DMARC#:glossary) est conforme à BIMI **MAIS** n'a pas d'**adresse de rapport agrégé** (rua).",
|
|
2030
|
+
"investigate.card.dmarc.dmarc-no-rua_md": "[](DMARC#:glossary) n'a pas d'**adresse de rapport agrégé** (rua).",
|
|
2028
2031
|
"investigate.card.dmarc.dmarc-invalid-record_md": "[](DMARC#:glossary) a un enregistrement **invalide** .",
|
|
2029
|
-
"investigate.card.dmarc.dmarc-failed_md": "[](DMARC#:glossary) **ne sont pas** conformes
|
|
2032
|
+
"investigate.card.dmarc.dmarc-failed_md": "Les paramètres [](DMARC#:glossary) **ne sont pas** conformes à BIMI",
|
|
2030
2033
|
"investigate.card.dmarc.misplaced-policy_md": "**Le [FULL](DMARC_POLICY#:glossary) est mal placé.",
|
|
2031
2034
|
"investigate.card.dmarc.misplaced-bad-policy_md": "**Le [FULL](DMARC_POLICY#:glossary) est mal placé et a une erreur de syntaxe:**",
|
|
2032
2035
|
"investigate.card.dmarc.bad-policy_md_one": "**Le [FULL](DMARC_POLICY#:glossary) présente l'erreur de syntaxe suivante:**.",
|
|
@@ -2049,7 +2052,7 @@ var frFR = {
|
|
|
2049
2052
|
"investigate.card.dmarc.void-dynamic-dmarc-ns-record": "L'enregistrement DMARC dynamique (NS) est ajouté mais les Dynamic Services ne sont pas activés.",
|
|
2050
2053
|
"investigate.card.dmarc.policy-none-pct-0_md": "Aucun rapport ne sera envoyé avec cette politique, car le taux d'échantillonnage [](PCT#:glossary) est fixé à `0`.",
|
|
2051
2054
|
"investigate.card.dmarc.policy-not-none-pct-0_md": "Tous les emails **non autorisés** seront délivrés aux destinataires bien que la politique soit définie sur [{policy}](DMARC_POLICY#:glossary), parce que le taux d'échantillonnage [](PCT#:glossary) est défini sur `0`.",
|
|
2052
|
-
"investigate.card.dmarc.policy-inherited-from-org-domain_md": "La politique DMARC du [](FROM#:glossary) `{fromDomain}` provient du [](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
|
|
2055
|
+
"investigate.card.dmarc.policy-inherited-from-org-domain_md": "La politique DMARC du [](FROM#:glossary) `{fromDomain}` provient du [domaine organisationnel](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
|
|
2053
2056
|
"investigate.card.dmarc.category-policy-good_md": "La politique de votre DNS est conforme à la spécification DMARC.",
|
|
2054
2057
|
"investigate.card.dmarc.all-emails-reported_md": "Tous les courriels seront [REPORTED](DMARC_POLICY#:glossary).",
|
|
2055
2058
|
"investigate.card.dmarc.all-emails-reported-only_md": "DMARC configuré en mode rapport uniquement. Tous les courriels seront uniquement [REPORTED](DMARC_POLICY#:glossary).",
|
|
@@ -2104,21 +2107,21 @@ var frFR = {
|
|
|
2104
2107
|
"investigate.card.subdo.blog": "Vous pouvez obtenir un aperçu complet de votre configuration SPF sur notre vérificateur SPF, ou en savoir plus sur l'attaque SubdoMailing sur notre base de connaissances.",
|
|
2105
2108
|
"investigate.card.subdo.spf-subdo-poisoned-include_md": "Inclusion compromise : **{raw}**",
|
|
2106
2109
|
"investigate.card.subdo.spf-subdo-poisoned-mechanism_md": "Inclusion compromise : **{raw}**",
|
|
2107
|
-
"investigate.card.spf.spf-no-spf_md": "
|
|
2110
|
+
"investigate.card.spf.spf-no-spf_md": "Aucun SPF configuré pour le domaine.",
|
|
2108
2111
|
"investigate.card.spf.spf-ipf-error-spf_md": "Erreur SPF : {msg}",
|
|
2109
2112
|
"investigate.card.spf.spf-extra-spf-lookups_md": "Trop de recherches SPF ({lookupCount} trouvées, 10 autorisées)",
|
|
2110
2113
|
"investigate.card.spf.spf-multiple-mx-providers_md": "Plusieurs fournisseurs de messagerie configurés dans les enregistrements MX de ce domaine.",
|
|
2111
|
-
"investigate.card.spf.spf-multiple-spf_md": "Plusieurs enregistrements SPF
|
|
2112
|
-
"investigate.card.spf.spf-multiple-all_md": "Plusieurs mécanismes **
|
|
2113
|
-
"investigate.card.spf.spf-recursive-include-spf_md": "L'inclusion SPF redondante peut être supprimée car elle
|
|
2114
|
-
"investigate.card.spf.spf-v-tag-duplicated-spf_md": "La balise de
|
|
2115
|
-
"investigate.card.spf.spf-all-not-last_md": "Toute donnée
|
|
2116
|
-
"investigate.card.spf.spf-all-and-redirect_md": "Toute **redirection** est ignorée s'il
|
|
2117
|
-
"investigate.card.spf.spf-all-plus-warning_md": "Le mécanisme **+all** ne
|
|
2114
|
+
"investigate.card.spf.spf-multiple-spf_md": "Plusieurs enregistrements SPF trouvés pour ce domaine",
|
|
2115
|
+
"investigate.card.spf.spf-multiple-all_md": "Plusieurs mécanismes **all** dans l'enregistrement SPF",
|
|
2116
|
+
"investigate.card.spf.spf-recursive-include-spf_md": "L'inclusion SPF redondante peut être supprimée car elle pointe vers votre propre domaine",
|
|
2117
|
+
"investigate.card.spf.spf-v-tag-duplicated-spf_md": "La balise de version est dupliquée.",
|
|
2118
|
+
"investigate.card.spf.spf-all-not-last_md": "Toute donnée après le **all** est ignorée.",
|
|
2119
|
+
"investigate.card.spf.spf-all-and-redirect_md": "Toute **redirection** est ignorée s'il y a un mécanisme **all**.",
|
|
2120
|
+
"investigate.card.spf.spf-all-plus-warning_md": "Le mécanisme **+all** ne devrait pas être utilisé car il autorise toutes les adresses IP d'envoi.",
|
|
2118
2121
|
"investigate.card.spf.spf-all-question-mark-warning_md": "**?all** ne doit pas être utilisé car il permet une réponse ambiguë.",
|
|
2119
|
-
"investigate.card.spf.spf-no-all_md": "Il est recommandé d'utiliser un modificateur **redirect**
|
|
2120
|
-
"investigate.card.spf.spf-invalid-spf1-header_md": "Enregistrement SPF
|
|
2121
|
-
"investigate.card.spf.spf-invalid-spf-header-include_md": "L'enregistrement SPF
|
|
2122
|
+
"investigate.card.spf.spf-no-all_md": "Il est recommandé d'utiliser soit un modificateur **redirect** soit un mécanisme **all** pour terminer explicitement le traitement.",
|
|
2123
|
+
"investigate.card.spf.spf-invalid-spf1-header_md": "Enregistrement SPF invalide",
|
|
2124
|
+
"investigate.card.spf.spf-invalid-spf-header-include_md": "L'enregistrement SPF est invalide car il a un espace entre l'instruction include:.",
|
|
2122
2125
|
"investigate.card.spf.spf-ip-subnet-mask-too-small_md": "Le sous-réseau **{raw}** permet à un grand nombre d'hôtes d'envoyer du courrier électronique au nom du domaine.",
|
|
2123
2126
|
"investigate.card.spf.spf-ip-subnet-overlaps-public-range_md": "Le sous-réseau **{raw}** chevauche la plage **{value}**, appartenant à un fournisseur de cloud accessible au public : **{sourceRef}**.",
|
|
2124
2127
|
"investigate.card.spf.spf-directive-permissive-exists_md": "Le mécanisme **{raw}** a été défini pour permettre à n'importe quel hôte d'envoyer un courrier électronique au nom du domaine.",
|
|
@@ -2641,6 +2644,7 @@ var frFR = {
|
|
|
2641
2644
|
"investigate.section-titles.bimi-domain": "Domaines DMARC",
|
|
2642
2645
|
"investigate.section-titles.dmarc-domain": "Domaines DMARC",
|
|
2643
2646
|
"investigate.card.dmarc.domain-aligned": "Le domaine est aligné avec le domaine expéditeur. Il peut contribuer à la réussite de DMARC.",
|
|
2647
|
+
"investigate.card.dmarc.domain-aligned-not-verified": "Le domaine est aligné avec le domaine expéditeur, mais sa signature n'a pas été vérifiée, il ne peut donc pas contribuer à la réussite de DMARC.",
|
|
2644
2648
|
"investigate.card.dmarc.domain-not-aligned": "Le domaine n'est pas aligné avec le domaine expéditeur. Il ne peut pas contribuer à la réussite de DMARC."
|
|
2645
2649
|
};
|
|
2646
2650
|
|
|
@@ -6982,6 +6986,206 @@ function renderList(items) {
|
|
|
6982
6986
|
}, item)));
|
|
6983
6987
|
}
|
|
6984
6988
|
|
|
6989
|
+
// =============================================================================
|
|
6990
|
+
// STATUS LIST RENDERING
|
|
6991
|
+
// =============================================================================
|
|
6992
|
+
|
|
6993
|
+
/**
|
|
6994
|
+
* Status-list container. Mirrors the plain `<ul>` that `renderList` emits so the
|
|
6995
|
+
* two list variants sit at the same indent and rhythm, adding only the per-entry
|
|
6996
|
+
* row layout the status icons need.
|
|
6997
|
+
*/
|
|
6998
|
+
const StyledStatusList = styled.ul`
|
|
6999
|
+
li {
|
|
7000
|
+
display: flex;
|
|
7001
|
+
align-items: center;
|
|
7002
|
+
gap: 0.35em;
|
|
7003
|
+
min-width: 0;
|
|
7004
|
+
}
|
|
7005
|
+
`;
|
|
7006
|
+
|
|
7007
|
+
/**
|
|
7008
|
+
* The entry's text (a domain, on the alignment row).
|
|
7009
|
+
*
|
|
7010
|
+
* `min-width: 0` has to be on the text itself, not just the `li`: a flex item
|
|
7011
|
+
* defaults to `min-width: auto`, which refuses to shrink below its content's
|
|
7012
|
+
* intrinsic width, so a long unbroken domain would push the status icon
|
|
7013
|
+
* (`flex-shrink: 0`) out of the row. `overflow-wrap: anywhere` lets a label with no
|
|
7014
|
+
* natural break opportunity wrap instead of overflowing.
|
|
7015
|
+
*
|
|
7016
|
+
* A dedicated component rather than an `li > span` descendant selector, which would
|
|
7017
|
+
* also be matched by the strike wrapper if the markup were ever reshuffled.
|
|
7018
|
+
*/
|
|
7019
|
+
const StyledStatusListText = styled.span`
|
|
7020
|
+
min-width: 0;
|
|
7021
|
+
overflow-wrap: anywhere;
|
|
7022
|
+
`;
|
|
7023
|
+
|
|
7024
|
+
// A div, not a span: TooltipInPortalWrapper renders a <div> (TooltipInPortal.tsx),
|
|
7025
|
+
// and a <div> inside a <span> is invalid HTML — span is phrasing content, div is
|
|
7026
|
+
// flow content. `display: inline-flex` keeps it laid out inline regardless. This
|
|
7027
|
+
// matches the row-level alignment icon in SignalCardExtractedItems, which wraps
|
|
7028
|
+
// the same tooltip in a div for the same reason.
|
|
7029
|
+
const StyledStatusListIcon = styled.div`
|
|
7030
|
+
display: inline-flex;
|
|
7031
|
+
align-items: center;
|
|
7032
|
+
flex-shrink: 0;
|
|
7033
|
+
`;
|
|
7034
|
+
|
|
7035
|
+
/**
|
|
7036
|
+
* Diagonal strike across a status icon, for the not-aligned case.
|
|
7037
|
+
*
|
|
7038
|
+
* Drawn in CSS over the mdi glyph rather than inlining a bespoke crossed-out SVG,
|
|
7039
|
+
* so it scales with the glyph and the DS keeps a single source for the underlying
|
|
7040
|
+
* icon.
|
|
7041
|
+
*
|
|
7042
|
+
* The colour is declared **here, once**, on the wrapper: the `::after` gradient
|
|
7043
|
+
* picks it up through `currentColor`, and the `Icon` inside is passed
|
|
7044
|
+
* `currentColor` too, so glyph and strike cannot drift apart. Setting it on the
|
|
7045
|
+
* pseudo-element instead would make `currentColor` resolve to that local
|
|
7046
|
+
* declaration rather than being inherited, which is the trap this replaced.
|
|
7047
|
+
*/
|
|
7048
|
+
const StyledStruckThrough = styled.span`
|
|
7049
|
+
position: relative;
|
|
7050
|
+
display: inline-flex;
|
|
7051
|
+
align-items: center;
|
|
7052
|
+
color: ${COLOR.main.red};
|
|
7053
|
+
|
|
7054
|
+
&::after {
|
|
7055
|
+
content: '';
|
|
7056
|
+
position: absolute;
|
|
7057
|
+
inset: 0;
|
|
7058
|
+
background: linear-gradient(
|
|
7059
|
+
to top right,
|
|
7060
|
+
transparent calc(50% - 0.7px),
|
|
7061
|
+
currentColor calc(50% - 0.7px),
|
|
7062
|
+
currentColor calc(50% + 0.7px),
|
|
7063
|
+
transparent calc(50% + 0.7px)
|
|
7064
|
+
);
|
|
7065
|
+
pointer-events: none;
|
|
7066
|
+
}
|
|
7067
|
+
`;
|
|
7068
|
+
|
|
7069
|
+
/**
|
|
7070
|
+
* Status icon for a single {@link StatusListItem}.
|
|
7071
|
+
*
|
|
7072
|
+
* Mirrors the icon vocabulary `getIcon` uses for row-level statuses in
|
|
7073
|
+
* SignalCardExtractedItems, so a per-entry tick reads identically to a row-level
|
|
7074
|
+
* one. A `''` status means "no status" — the entry renders as plain text, exactly
|
|
7075
|
+
* as a `ListData` entry would.
|
|
7076
|
+
*
|
|
7077
|
+
* Per Antony's spec (2026-08-06) this row uses an **align** glyph rather than a
|
|
7078
|
+
* generic tick/cross, with the not-aligned case struck through:
|
|
7079
|
+
*
|
|
7080
|
+
* | status | `matchMode` | meaning | render |
|
|
7081
|
+
* | --- | --- | --- | --- |
|
|
7082
|
+
* | `good`/`warning` | `exact` | signing domain equals the From domain | green `FormatAlignJustify` |
|
|
7083
|
+
* | `good`/`warning` | `organizational` | shares the Organizational Domain | green `FormatAlignRight` |
|
|
7084
|
+
* | `danger` | — | does not align | red `FormatAlignRight`, struck through |
|
|
7085
|
+
*
|
|
7086
|
+
* **`warning` renders as aligned, not amber.** This row is about *domains*, not
|
|
7087
|
+
* mechanism results (Antony, option (a), 2026-08-06). signal-logic's `warning`
|
|
7088
|
+
* branch fires on `aligned && !verified`, where `verified` is
|
|
7089
|
+
* `signature.code === 'pass'` — a property of the crypto, not of the domain pair —
|
|
7090
|
+
* and the DKIM card already reports verification per signature. Colouring it
|
|
7091
|
+
* differently here would make one icon answer two questions and duplicate the DKIM
|
|
7092
|
+
* card, inviting the two to disagree. The distinct `tooltipKey`
|
|
7093
|
+
* (`card.dmarc.domain-aligned-not-verified`) is preserved, so the detail is still
|
|
7094
|
+
* available on hover.
|
|
7095
|
+
*
|
|
7096
|
+
* The known cost: an aligned-but-unverified domain cannot actually contribute to
|
|
7097
|
+
* DMARC passing, so green slightly overstates it. That is conveyed by the
|
|
7098
|
+
* "Alignment Check" prose rows and the overall card verdict, which read as failed —
|
|
7099
|
+
* the honest place for it, rather than overloading a per-domain icon.
|
|
7100
|
+
*
|
|
7101
|
+
* **Exact vs organizational** comes from `matchMode`, added upstream by WEB-1203.
|
|
7102
|
+
* An exact match is *inherently* aligned under either `adkim` mode, so it earns the
|
|
7103
|
+
* fully-justified glyph; an Organizational Domain match only aligns under `adkim=r`
|
|
7104
|
+
* and gets the right-aligned one. `matchMode` is absent on non-aligned entries and
|
|
7105
|
+
* on the fallback `ListData` shape, so a missing value degrades to `FormatAlignRight`
|
|
7106
|
+
* rather than guessing.
|
|
7107
|
+
*/
|
|
7108
|
+
function renderItemStatusIcon(status, matchMode, accessibleName) {
|
|
7109
|
+
// The DS `Icon` is decorative by default — it sets aria-hidden unless given an
|
|
7110
|
+
// aria-label. The status here is not decorative: it is the only thing telling a
|
|
7111
|
+
// reader whether the domain aligned, and the tooltip carrying that meaning is
|
|
7112
|
+
// hover-only. So pass the translated tooltip text as the accessible name, with
|
|
7113
|
+
// `aria-hidden={false}` to un-hide it, and fall back to the bare status word
|
|
7114
|
+
// when an entry supplies no tooltipKey.
|
|
7115
|
+
const label = accessibleName !== null && accessibleName !== void 0 ? accessibleName : status || undefined;
|
|
7116
|
+
const aria = label ? {
|
|
7117
|
+
'aria-label': label,
|
|
7118
|
+
'aria-hidden': false
|
|
7119
|
+
} : {};
|
|
7120
|
+
switch (status) {
|
|
7121
|
+
// `warning` is deliberately identical to `good` — see above.
|
|
7122
|
+
case 'good':
|
|
7123
|
+
case 'warning':
|
|
7124
|
+
return /*#__PURE__*/React__default.createElement(Icon, _extends({
|
|
7125
|
+
icon: matchMode === 'exact' ? mdiFormatAlignJustify : mdiFormatAlignRight,
|
|
7126
|
+
color: COLOR.main.green,
|
|
7127
|
+
size: "small"
|
|
7128
|
+
}, aria));
|
|
7129
|
+
case 'danger':
|
|
7130
|
+
// Struck through: aligned-looking glyph, cancelled. `currentColor` so the
|
|
7131
|
+
// glyph and the strike share the one colour declared on the wrapper.
|
|
7132
|
+
return /*#__PURE__*/React__default.createElement(StyledStruckThrough, null, /*#__PURE__*/React__default.createElement(Icon, _extends({
|
|
7133
|
+
icon: mdiFormatAlignRight,
|
|
7134
|
+
color: "currentColor",
|
|
7135
|
+
size: "small"
|
|
7136
|
+
}, aria)));
|
|
7137
|
+
default:
|
|
7138
|
+
return null;
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7141
|
+
|
|
7142
|
+
/**
|
|
7143
|
+
* One status-list entry: its text, plus its own status icon and tooltip.
|
|
7144
|
+
*
|
|
7145
|
+
* A component rather than a plain render call because the tooltip text needs
|
|
7146
|
+
* translating, and `renderStructuredData` is a pure function called from the
|
|
7147
|
+
* adapter layer outside React — so it has no `t` to pass down. Resolving the key
|
|
7148
|
+
* here keeps the renderer's signature unchanged and matches how the rest of the
|
|
7149
|
+
* card resolves strings.
|
|
7150
|
+
*/
|
|
7151
|
+
const StatusListEntry = _ref => {
|
|
7152
|
+
let {
|
|
7153
|
+
item
|
|
7154
|
+
} = _ref;
|
|
7155
|
+
const {
|
|
7156
|
+
t
|
|
7157
|
+
} = useSignalCardTranslation('investigate');
|
|
7158
|
+
const tooltip = item.tooltipKey ? t(item.tooltipKey) : undefined;
|
|
7159
|
+
// The tooltip text doubles as the icon's accessible name — see renderItemStatusIcon.
|
|
7160
|
+
const icon = renderItemStatusIcon(item.status, item.matchMode, tooltip);
|
|
7161
|
+
return /*#__PURE__*/React__default.createElement("li", null, /*#__PURE__*/React__default.createElement(StyledStatusListText, null, item.text), icon ? /*#__PURE__*/React__default.createElement(StyledStatusListIcon, null, tooltip ? /*#__PURE__*/React__default.createElement(TooltipInPortalWrapper, {
|
|
7162
|
+
message: tooltip
|
|
7163
|
+
}, /*#__PURE__*/React__default.createElement("span", {
|
|
7164
|
+
style: {
|
|
7165
|
+
display: 'inline-flex'
|
|
7166
|
+
}
|
|
7167
|
+
}, icon)) : icon) : null);
|
|
7168
|
+
};
|
|
7169
|
+
|
|
7170
|
+
/**
|
|
7171
|
+
* Render a list whose entries each carry their own status.
|
|
7172
|
+
*
|
|
7173
|
+
* Used by the DMARC card's "DKIM Signing Domain" row, where one message can
|
|
7174
|
+
* carry several DKIM signatures whose alignment differs — an aligned domain gets a
|
|
7175
|
+
* green align glyph while a non-aligned one gets a red struck-through glyph, in the
|
|
7176
|
+
* same list. The row-level status is cleared by signal-logic whenever per-entry
|
|
7177
|
+
* statuses are present, so only these icons render and nothing double-counts.
|
|
7178
|
+
*/
|
|
7179
|
+
function renderStatusList(items) {
|
|
7180
|
+
if (!(items !== null && items !== void 0 && items.length)) {
|
|
7181
|
+
return null;
|
|
7182
|
+
}
|
|
7183
|
+
return /*#__PURE__*/React__default.createElement(StyledStatusList, null, items.map((item, i) => /*#__PURE__*/React__default.createElement(StatusListEntry, {
|
|
7184
|
+
key: i,
|
|
7185
|
+
item: item
|
|
7186
|
+
})));
|
|
7187
|
+
}
|
|
7188
|
+
|
|
6985
7189
|
// =============================================================================
|
|
6986
7190
|
// SIGNED HEADERS RENDERING
|
|
6987
7191
|
// =============================================================================
|
|
@@ -7180,6 +7384,13 @@ function renderStructuredData(data) {
|
|
|
7180
7384
|
}
|
|
7181
7385
|
case 'list':
|
|
7182
7386
|
return renderList(data.items);
|
|
7387
|
+
|
|
7388
|
+
// Additive: `list` above still ships. signal-logic falls back to it whenever
|
|
7389
|
+
// per-signature alignment cannot be computed (Domain Analyzer / `fromDns`
|
|
7390
|
+
// results, no `fromDomain`, or a domain sourced from the `dmarc.dkimDomain`
|
|
7391
|
+
// scalar rather than a signature), so both variants must keep working.
|
|
7392
|
+
case 'status-list':
|
|
7393
|
+
return renderStatusList(data.items);
|
|
7183
7394
|
case 'highlighted-text':
|
|
7184
7395
|
return /*#__PURE__*/React__default.createElement(WordHighlighter$1, {
|
|
7185
7396
|
searchWord: data.searchWord,
|