diff --git a/packages/manager/apps/zimbra/package.json b/packages/manager/apps/zimbra/package.json index 0464deb17f60..d394c3808148 100644 --- a/packages/manager/apps/zimbra/package.json +++ b/packages/manager/apps/zimbra/package.json @@ -62,7 +62,7 @@ "vitest": "^2.1.2" }, "regions": [ - "CA", - "EU" + "EU", + "CA" ] } diff --git a/packages/manager/apps/zimbra/public/translations/dashboard/Messages_fr_FR.json b/packages/manager/apps/zimbra/public/translations/dashboard/Messages_fr_FR.json index d8b8cf39ab71..84790d3a9a16 100644 --- a/packages/manager/apps/zimbra/public/translations/dashboard/Messages_fr_FR.json +++ b/packages/manager/apps/zimbra/public/translations/dashboard/Messages_fr_FR.json @@ -4,7 +4,7 @@ "zimbra_dashboard_domains_add": "Ajouter un domaine", "zimbra_dashboard_domains_edit": "Configuration du domaine", "zimbra_dashboard_domains_delete": "Supprimer un domaine", - "zimbra_dashboard_domains_diagnostic": "Diagnostique", + "zimbra_dashboard_domains_diagnostics": "Diagnostiques", "zimbra_dashboard_email_accounts": "Compte email", "zimbra_dashboard_email_accounts_add": "Créer un compte email", "zimbra_dashboard_email_accounts_edit": "Modifier le compte", diff --git a/packages/manager/apps/zimbra/public/translations/domains/Messages_fr_FR.json b/packages/manager/apps/zimbra/public/translations/domains/Messages_fr_FR.json index 67aa9374151e..9045052a5b91 100644 --- a/packages/manager/apps/zimbra/public/translations/domains/Messages_fr_FR.json +++ b/packages/manager/apps/zimbra/public/translations/domains/Messages_fr_FR.json @@ -1,14 +1,13 @@ { "zimbra_domains_add_domain_title": "Ajouter un domaine", "zimbra_domains_datagrid_account_label": "Nombre de comptes", - "zimbra_domains_datagrid_diagnostic_label": "Diagnostique", "zimbra_domains_datagrid_diagnostic_configuration_ok": "Configuration OK", - "zimbra_domains_datagrid_diagnostic_tooltip_title": "Diagnostic {{ diagType }}", "zimbra_domains_datagrid_domain_label": "Domaine", "zimbra_domains_datagrid_organization_label": "Organisation", "zimbra_domains_datagrid_status_label": "Statut", "zimbra_domains_datagrid_account_number": "Nombre de comptes", "zimbra_domains_tooltip_configure": "Configurer", "zimbra_domains_tooltip_delete": "Supprimer", + "zimbra_domains_tooltip_diagnostics": "Diagnostiques", "zimbra_domains_tooltip_need_organization": "Veuillez d'abord configurer une organisation" } diff --git a/packages/manager/apps/zimbra/public/translations/domains/diagnostic/Messages_fr_FR.json b/packages/manager/apps/zimbra/public/translations/domains/diagnostic/Messages_fr_FR.json index ded8a920ff98..3efe74b6261c 100644 --- a/packages/manager/apps/zimbra/public/translations/domains/diagnostic/Messages_fr_FR.json +++ b/packages/manager/apps/zimbra/public/translations/domains/diagnostic/Messages_fr_FR.json @@ -1,37 +1,19 @@ { - "zimbra_domain_modal_diagnostic_guide": "guide", - "zimbra_domain_modal_diagnostic_srv_title": "Diagnostique SRV", - "zimbra_domain_modal_diagnostic_srv_content_header": "Pour faciliter la configuration des boites mails sur vos terminaux, veuillez ajouter cette configuration à votre DNS (consulter notre )", - "zimbra_domain_modal_diagnostic_srv_content_header_ovh_hosted_domain_part1": "La configuration SRV permet la configuration automatique de vos boites mails sur vos clients de messagerie.", - "zimbra_domain_modal_diagnostic_srv_content_header_ovh_hosted_domain_part2": "Votre domaine est hébergé chez OVH Cloud, nous allons procéder à la configuration de votre champ SRV.", - "zimbra_domain_modal_diagnostic_srv_domain": "Domaine", - "zimbra_domain_modal_diagnostic_fields": "Champs", - "zimbra_domain_modal_diagnostic_srv_action_validate": "Valider", - "zimbra_domain_modal_diagnostic_srv_action_cancel": "Annuler", - "zimbra_domain_modal_diagnostic_srv_action_close": "Fermer", - "zimbra_domain_modal_diagnostic_field_subdomain": "Sous domaine :", - "zimbra_domain_modal_diagnostic_field_priority": "Priorité :", - "zimbra_domain_modal_diagnostic_field_weight": "Poids :", - "zimbra_domain_modal_diagnostic_field_port": "Port :", - "zimbra_domain_modal_diagnostic_field_target": "Cible :", - "zimbra_domain_modal_diagnostic_mx_title": "Diagnostique MX", - "zimbra_domain_modal_diagnostic_mx_content_header_part1": "La configuration de l'enregistrement MX permet de recevoir vos emails dans vos boites mail.", - "zimbra_domain_modal_diagnostic_mx_content_header_part2": "Votre domaine n'étant pas géré par OVH Cloud, cette action doit être effectuée manuellement.", - "zimbra_domain_modal_diagnostic_mx_content_header_part3": "Veuillez créer les champs MX sur ce domaine en donnant les mêmes informations que ci-dessous.", - "zimbra_domain_modal_diagnostic_mx_content_header_ovh_hosted_domain_part1": "La configuration de l'enregistrement MX permet de recevoir vos emails dans vos boites mail.", - "zimbra_domain_modal_diagnostic_mx_content_header_ovh_hosted_domain_part2": "Votre domaine étant hébergé chez OVH Cloud, nous allons effectuer la configuration pour vous.", - "zimbra_domain_modal_diagnostic_mx_content_header_ovh_hosted_domain_part3": "Souhaitez-vous commencer à recevoir les mails du domaine {{domain}} sur ce service ?", - "zimbra_domain_modal_diagnostic_mx_content_footer": "En cas de problème, consultez la documentation de votre fournisseur de domaine.", - "zimbra_domain_modal_diagnostic_mx_action_validate": "Confirmer", - "zimbra_domain_modal_diagnostic_mx_action_test": "Tester", - "zimbra_domain_modal_diagnostic_mx_action_cancel": "Annuler", - "zimbra_domain_modal_diagnostic_mx_action_close": "Fermer", - "zimbra_domain_modal_diagnostic_spf_title": "Diagnostique SPF", - "zimbra_domain_modal_diagnostic_spf_content_header": "Si le domaine n'est pas géré par le même identifiant client que le service Exchange ou si votre domaine n'est pas hébergé chez OVHcloud, la configuration automatique n'est pas possible. Veuillez créer le champ SPF sur ce domaine manuellement en donnant les mêmes informations que ci-dessous.", - "zimbra_domain_modal_diagnostic_spf_content_header_ovh_hosted_domain_part1": "SPF permet de valider qu'OVHCloud est un émetteur légitime de mails avec votre nom de domaine.", - "zimbra_domain_modal_diagnostic_spf_content_header_ovh_hosted_domain_part2": "Votre domaine étant hébergé chez OVHCloud, nous pouvons procéder à la configuration automatique.", - "zimbra_domain_modal_diagnostic_spf_content_header_ovh_hosted_domain_part3": "Souhaitez-vous effectuer la configuration ?", - "zimbra_domain_modal_diagnostic_spf_action_cancel": "Annuler", - "zimbra_domain_modal_diagnostic_spf_action_close": "Fermer", - "zimbra_domain_modal_diagnostic_spf_action_validate": "Ok" + "zimbra_domain_diagnostic_cta_back": "Retour aux domaines", + "zimbra_domain_diagnostic_cta_refresh": "Rafraîchir les diagnostiques", + "zimbra_domain_diagnostic_status": "Statut :", + "zimbra_domain_diagnostic_status_ok": "Configuration OK", + "zimbra_domain_diagnostic_status_error": "Erreur de configuration", + "zimbra_domain_diagnostic_status_updating": "En cours de configuration", + "zimbra_domain_diagnostic_status_to_configure": "Pas configuré", + "zimbra_domain_diagnostic_status_disabled": "Désactivé", + "zimbra_domain_diagnostic_type": "Type :", + "zimbra_domain_diagnostic_access_guide": "Accéder au guide", + "zimbra_domain_diagnostic_subtitle": "Diagnostiques du domaine {{domain}}", + "zimbra_domain_diagnostic_field_host": "Nom/Hôte :", + "zimbra_domain_diagnostic_field_subdomain": "Sous domaine :", + "zimbra_domain_diagnostic_field_priority": "Priorité :", + "zimbra_domain_diagnostic_field_weight": "Poids :", + "zimbra_domain_diagnostic_field_port": "Port :", + "zimbra_domain_diagnostic_field_target": "Cible :" } diff --git a/packages/manager/apps/zimbra/src/api/_mock_/domain.ts b/packages/manager/apps/zimbra/src/api/_mock_/domain.ts index 23feb81d5869..a0e52a1c1759 100644 --- a/packages/manager/apps/zimbra/src/api/_mock_/domain.ts +++ b/packages/manager/apps/zimbra/src/api/_mock_/domain.ts @@ -1,4 +1,4 @@ -import { DomainType } from '@/api/domain'; +import { DiagnosticResponse, DomainType } from '@/api/domain'; import { ResourceStatus } from '@/api/api.type'; export const domainDetailMock: DomainType = { @@ -118,3 +118,35 @@ export const domainsMock: DomainType[] = [ ]; export const domainZone: string[] = ['test.fr', 'mydomain.fr', 'domain.fr']; + +export const domainDiagnosticMock = { + diagnostic: { + dkim: { + errorCode: 'INCORRECT_CNAME_RECORD', + errorMessage: 'string', + recordsFound: ['string'], + status: 'ERROR', + }, + errorCode: 'DNS_TIMEOUT', + errorMessage: 'string', + mx: { + errorCode: 'MISSING_VALID_MX_RECORD', + errorMessage: 'string', + recordsFound: [ + { + priority: 0, + target: 'string', + }, + ], + status: 'ERROR', + }, + spf: { + recordFound: 'string', + status: 'OK', + }, + status: 'ERROR', + }, + domainId: '19386dad-2e50-4000-8838-d6c283317e01', + domainName: 'string', + id: '19386dad-2e50-4000-86d3-0698199f4a01', +} as DiagnosticResponse; diff --git a/packages/manager/apps/zimbra/src/api/domain/api.ts b/packages/manager/apps/zimbra/src/api/domain/api.ts index 79503126a691..c5bffa6b0095 100644 --- a/packages/manager/apps/zimbra/src/api/domain/api.ts +++ b/packages/manager/apps/zimbra/src/api/domain/api.ts @@ -1,6 +1,7 @@ import { fetchIcebergV2, v2, v6 } from '@ovh-ux/manager-core-api'; import { DomainBodyParamsType, DomainType } from './type'; import { getApiPath } from '../utils/apiPath'; +import { domainDiagnosticMock } from '../_mock_'; // GET @@ -36,6 +37,24 @@ export const getZimbraPlatformDomainDetail = async ( return data; }; +export const getZimbraPlatformDomainDiagnostic = async ( + platformId: string, + domainId: string, + mock = true, +) => { + if (mock) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(domainDiagnosticMock); + }, 1000); + }); + } + const { data } = await v2.get( + `${getApiPath(platformId)}domain/${domainId}/diagnostic`, + ); + return data; +}; + // POST export const postZimbraDomain = async ( diff --git a/packages/manager/apps/zimbra/src/api/domain/key.ts b/packages/manager/apps/zimbra/src/api/domain/key.ts index 71a1bb986312..5c945e009ad7 100644 --- a/packages/manager/apps/zimbra/src/api/domain/key.ts +++ b/packages/manager/apps/zimbra/src/api/domain/key.ts @@ -5,6 +5,11 @@ export const getZimbraPlatformDomainsQueryKey = ( `get/zimbra/platform/${platformId}/domain?organizationId=${organizationId}`, ]; +export const getZimbraPlatformDomainDiagnosticQueryKey = ( + platformId: string, + domainId: string, +) => [`get/zimbra/platform/${platformId}/domain/${domainId}/diagnostic`]; + export const getZimbraPlatformDomainQueryKey = ( platformId: string, domainId: string, diff --git a/packages/manager/apps/zimbra/src/api/domain/type.ts b/packages/manager/apps/zimbra/src/api/domain/type.ts index 00fa7ea16a16..8c9a49e8c89d 100644 --- a/packages/manager/apps/zimbra/src/api/domain/type.ts +++ b/packages/manager/apps/zimbra/src/api/domain/type.ts @@ -38,3 +38,117 @@ export type DomainType = { organizationId: string; }; }; + +export type DnsDiagnostic = MXDiagnostic | SPFDiagnostic | DKIMDiagnostic; + +/** Error code of MX Diagnostics */ +export enum MXErrorCodeEnum { + 'MISSING_VALID_MX_RECORD' = 'MISSING_VALID_MX_RECORD', +} + +/** Object representing an MX record */ +export interface MXRecord { + /** Priority for that target server */ + priority: number; + /** Target server */ + target: string; +} + +/** Object representing a MX diagnostic */ +export interface MXDiagnostic { + /** Error code of the MX diagnostic */ + errorCode?: MXErrorCodeEnum; + /** Error message of the MX diagnostic */ + errorMessage?: string; + /** MX records found */ + recordsFound?: MXRecord[]; + /** Status of the MX diagnostic */ + status: DiagnosticStatusEnum; +} + +/** Error code of SPF Diagnostics */ +export enum SPFErrorCodeEnum { + 'MISSING_RECORD' = 'MISSING_RECORD', + 'OVH_NOT_INCLUDED' = 'OVH_NOT_INCLUDED', +} + +/** Object representing a SPF diagnostic */ +export interface SPFDiagnostic { + /** Error code of the SPF diagnostic */ + errorCode?: SPFErrorCodeEnum; + /** Error message of the SPF diagnostic */ + errorMessage?: string; + /** Record found for SPF */ + recordFound?: string; + /** Status of the SPF diagnostic */ + status: DiagnosticStatusEnum; +} + +/** Global status of the diagnostic */ +export enum GlobalDiagnosticStatusEnum { + 'ERROR' = 'ERROR', + 'OK' = 'OK', + 'PARTIAL' = 'PARTIAL', +} + +/** Error code for glopbal diagnostic */ +export enum GlobalDiagnosticErrorEnum { + 'DNS_TIMEOUT' = 'DNS_TIMEOUT', +} + +/** Status of the diagnostic */ +export enum DiagnosticStatusEnum { + 'DISABLED' = 'DISABLED', + 'ERROR' = 'ERROR', + 'OK' = 'OK', + 'TO_CONFIGURE' = 'TO_CONFIGURE', + 'UPDATING' = 'UPDATING', +} + +/** Error code of DKIM Diagnostics */ +export enum DKIMErrorCodeEnum { + 'INCORRECT_CNAME_RECORD' = 'INCORRECT_CNAME_RECORD', + 'MISSING_ONE_SELECTOR' = 'MISSING_ONE_SELECTOR', + 'OVH_NOT_INCLUDED' = 'OVH_NOT_INCLUDED', + 'TASK_FAILED' = 'TASK_FAILED', +} + +/** Object representing a diagnostic of DKIM */ +export interface DKIMDiagnostic { + /** Error code of the DKIM diagnostic */ + errorCode?: DKIMErrorCodeEnum; + /** Error message of the DKIM diagnostic */ + errorMessage?: string; + /** Records found in DNS identified as DKIM */ + recordsFound?: string[]; + /** Status of the DKIM diagnostic */ + status: DiagnosticStatusEnum; +} + +/** Diagnostic of a domain */ +export interface Diagnostic { + /** Diagnostic of DKIM fields */ + dkim?: DKIMDiagnostic; + /** Error code of the global diagnostic */ + errorCode?: GlobalDiagnosticErrorEnum; + /** Error message of the global diagnostic */ + errorMessage?: string; + /** Diagnostic of MX fields */ + mx?: MXDiagnostic; + /** Diagnostic of SPF field */ + spf?: SPFDiagnostic; + /** Global status of the diagnostic */ + status: GlobalDiagnosticStatusEnum; +} + +/** Response to a diagnostic request */ +export interface DiagnosticResponse { + /** Diagnostic of the domain */ + diagnostic: Diagnostic; + /** Id of the domain */ + domainId: string; + /** Name of the domain */ + domainName: string; + /** Id of the diagnostic */ + id: string; +} diff --git a/packages/manager/apps/zimbra/src/components/DiagnosticBadge.tsx b/packages/manager/apps/zimbra/src/components/DiagnosticBadge.tsx deleted file mode 100644 index 4e09a0c6395e..000000000000 --- a/packages/manager/apps/zimbra/src/components/DiagnosticBadge.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { OdsTooltip, OdsText, OdsBadge } from '@ovhcloud/ods-components/react'; -import { - ODS_BADGE_COLOR, - ODS_BADGE_SIZE, - ODS_TEXT_PRESET, -} from '@ovhcloud/ods-components'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { DnsRecordType } from '@/utils'; -import { useGenerateUrl } from '@/hooks'; - -type DiagnosticBadgeProps = { - diagType: DnsRecordType; - status?: ODS_BADGE_COLOR; - domainId?: string; -}; - -export const DiagnosticBadge: React.FC = ({ - diagType, - status, - domainId, -}) => { - const { t } = useTranslation('domains'); - const navigate = useNavigate(); - const hasAction = !!( - status === ODS_BADGE_COLOR.critical && - [DnsRecordType.MX, DnsRecordType.SPF, DnsRecordType.SRV].some( - (diag) => diag === diagType, - ) && - domainId - ); - - const href = useGenerateUrl('./diagnostic', 'path', { - domainId, - dnsRecordType: diagType, - isOvhDomain: 'false', - }); - - const handleChipClick = (e: React.MouseEvent) => { - e.stopPropagation(); - navigate(href); - }; - - const id = `diag-tooltip-trigger-${diagType}-${domainId}`; - - return ( - <> -
- -
- - {status === ODS_BADGE_COLOR.success && ( - -
- - {t('zimbra_domains_datagrid_diagnostic_tooltip_title', { - diagType, - })} - - - {t('zimbra_domains_datagrid_diagnostic_configuration_ok')} - -
-
- )} - - ); -}; diff --git a/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/Dashboard.tsx b/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/Dashboard.tsx index 34245279f673..8ac7cbe2dfb9 100644 --- a/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/Dashboard.tsx +++ b/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/Dashboard.tsx @@ -89,7 +89,10 @@ export const Dashboard: React.FC = () => { urls.domains, urls.domainsEdit, urls.domainsDelete, - urls.domains_diagnostic, + urls.domains_diagnostic_mx, + urls.domains_diagnostic_spf, + urls.domains_diagnostic_srv, + urls.domains_diagnostic_dkim, ], platformId, ), diff --git a/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/TabsPanel.tsx b/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/TabsPanel.tsx index b7042d924f21..a5687d43896b 100644 --- a/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/TabsPanel.tsx +++ b/packages/manager/apps/zimbra/src/components/layout-helpers/Dashboard/TabsPanel.tsx @@ -9,6 +9,7 @@ export type TabItemProps = { to: string; hidden?: boolean; isDisabled?: boolean; + component?: ReactNode; }; export type TabsProps = { @@ -60,6 +61,11 @@ const TabsPanel: React.FC = ({ tabs }) => { { + if (tab.isDisabled) { + e.preventDefault(); + } + }} className="no-underline" > { +type UseDomainParams = Omit & { + domainId: string; +}; + +export const useDomain = (params: UseDomainParams) => { + const { domainId, ...options } = params; const { platformId } = usePlatform(); return useQuery({ queryKey: getZimbraPlatformDomainQueryKey(platformId, domainId), queryFn: () => getZimbraPlatformDomainDetail(platformId, domainId), - enabled: !!platformId && !!domainId, - gcTime: noCache ? 0 : 5000, - }); + enabled: (query) => + (typeof options.enabled === 'function' + ? options.enabled(query) + : typeof options.enabled !== 'boolean' || options.enabled) && + !!platformId && + !!domainId, + ...options, + }) as UseQueryResult; }; diff --git a/packages/manager/apps/zimbra/src/hooks/useDomainDiagnostic.ts b/packages/manager/apps/zimbra/src/hooks/useDomainDiagnostic.ts new file mode 100644 index 000000000000..96228e9ccba2 --- /dev/null +++ b/packages/manager/apps/zimbra/src/hooks/useDomainDiagnostic.ts @@ -0,0 +1,36 @@ +import { + useQuery, + UseQueryOptions, + UseQueryResult, +} from '@tanstack/react-query'; +import { usePlatform } from '@/hooks'; + +import { + DiagnosticResponse, + getZimbraPlatformDomainDiagnostic, + getZimbraPlatformDomainDiagnosticQueryKey, +} from '@/api/domain'; + +type UseDomainDiagnosticParams = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' +> & { + domainId: string; +}; + +export const useDomainDiagnostic = (params: UseDomainDiagnosticParams) => { + const { domainId, ...options } = params; + const { platformId } = usePlatform(); + + return useQuery({ + queryKey: getZimbraPlatformDomainDiagnosticQueryKey(platformId, domainId), + queryFn: () => getZimbraPlatformDomainDiagnostic(platformId, domainId), + enabled: (query) => + (typeof options.enabled === 'function' + ? options.enabled(query) + : typeof options.enabled !== 'boolean' || options.enabled) && + !!platformId && + !!domainId, + ...options, + }) as UseQueryResult; +}; diff --git a/packages/manager/apps/zimbra/src/index.scss b/packages/manager/apps/zimbra/src/index.scss index 4f27d091e21d..dc6ad4e0fbf8 100644 --- a/packages/manager/apps/zimbra/src/index.scss +++ b/packages/manager/apps/zimbra/src/index.scss @@ -23,3 +23,13 @@ ods-modal::part(dialog) { ods-tag.org-tag::part(tag) { transform: translateY(-10%); } + +table.dns-fields { + tr { + height: 32px; + } +} + +.diag-dns-icon { + color: var(--ods-color-critical-400); +} diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ActionButtonDomain.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ActionButtonDomain.component.tsx index c71eb5bd0c53..33ccd3f006a4 100644 --- a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ActionButtonDomain.component.tsx +++ b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ActionButtonDomain.component.tsx @@ -7,6 +7,7 @@ import { DomainsItem } from './Domains'; import { useGenerateUrl, usePlatform } from '@/hooks'; import { IAM_ACTIONS } from '@/utils/iamAction.constants'; import { ResourceStatus } from '@/api/api.type'; +import { FEATURE_FLAGS } from '@/utils'; interface ActionButtonDomainProps { domainItem: DomainsItem; @@ -34,6 +35,14 @@ const ActionButtonDomain: React.FC = ({ navigate(hrefEditDomain); }; + const hrefDiagnosticsDomain = useGenerateUrl('./diagnostics/mx', 'path', { + domainId: domainItem.id, + }); + + const handleDiagnosticsDomainClick = () => { + navigate(hrefDiagnosticsDomain); + }; + const actionItems = [ { id: 1, @@ -44,6 +53,14 @@ const ActionButtonDomain: React.FC = ({ }, { id: 2, + onclick: handleDiagnosticsDomainClick, + label: t('zimbra_domains_tooltip_diagnostics'), + urn: platformUrn, + iamActions: [IAM_ACTIONS.domain.edit /* TODO diagnose */], + hidden: !FEATURE_FLAGS.DOMAIN_DIAGNOSTICS, + }, + { + id: 3, onclick: handleDeleteDomainClick, label: t('zimbra_domains_tooltip_delete'), urn: platformUrn, diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Diagnostics.page.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Diagnostics.page.tsx new file mode 100644 index 000000000000..93400fad522a --- /dev/null +++ b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Diagnostics.page.tsx @@ -0,0 +1,462 @@ +import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import { + LinkType, + Links, + IconLinkAlignmentType, + Subtitle, +} from '@ovh-ux/manager-react-components'; +import { useTranslation, UseTranslationResponse } from 'react-i18next'; +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; +import { + ODS_BADGE_COLOR, + ODS_BUTTON_VARIANT, + ODS_ICON_NAME, + ODS_LINK_COLOR, + ODS_MESSAGE_COLOR, + ODS_TEXT_PRESET, + JSX as Ods, +} from '@ovhcloud/ods-components'; +import { + OdsBadge, + OdsButton, + OdsClipboard, + OdsIcon, + OdsMessage, + OdsText, +} from '@ovhcloud/ods-components/react'; +import { StyleReactProps } from '@ovhcloud/ods-components/react/dist/types/react-component-lib/interfaces'; +import { useGenerateUrl, useDomain, usePlatform } from '@/hooks'; +import Loading from '@/components/Loading/Loading'; +import TabsPanel, { + activatedTabs, + computePathMatchers, + TabItemProps, +} from '@/components/layout-helpers/Dashboard/TabsPanel'; +import { urls } from '@/routes/routes.constants'; +import { useDomainDiagnostic } from '@/hooks/useDomainDiagnostic'; +import { DiagnosticStatusEnum, DnsDiagnostic } from '@/api/domain'; +import { DnsRecordType } from '@/utils/dnsconfig.constants'; +import GuideLink from '@/components/GuideLink'; +import { Guide, GUIDES_LIST } from '@/guides.constants'; + +// this should be in data.currentState.expectedDNSConfig.srv +const srvFields = { + subdomain: '_autodiscover._tcp', + priority: '0', + weight: '0', + port: '443', + target: 'ex5.mail.ovh.net.', +}; + +// this should be in data.currentState.expectedDNSConfig.mx +const mxFields = [ + { priority: 1, target: 'mx0.mail.ovh.net' }, + { priority: 5, target: 'mx1.mail.ovh.net' }, + { priority: 10, target: 'mx2.mail.ovh.net' }, + { priority: 100, target: 'mx3.mail.ovh.net' }, +]; + +// this should be in data.currentState.expectedDNSConfig.spf +const spfFields = { + subdomain: '_autodiscover._tcp', + target: '"v=spf1 include:mx.ovh.com ~all"', +}; + +// this should be in data.currentState.expectedDNSConfig.dkim +const dkimFields = [ + { + subdomain: 'testdkim1.fr', + target: '"v=dkim include:mx.ovh.com ~all"', + }, + { + subdomain: 'testdkim2.fr', + target: '"v=dkim include:mx2.ovh.com ~all"', + }, +]; + +const diagnosticHasError = (diagnostic: DnsDiagnostic) => { + return ( + diagnostic && + [DiagnosticStatusEnum.ERROR, DiagnosticStatusEnum.TO_CONFIGURE].includes( + diagnostic.status, + ) + ); +}; + +const getStatusBadgeColor = (status: DiagnosticStatusEnum): ODS_BADGE_COLOR => { + switch (status) { + case DiagnosticStatusEnum.OK: + return ODS_BADGE_COLOR.success; + case DiagnosticStatusEnum.ERROR: + return ODS_BADGE_COLOR.critical; + case DiagnosticStatusEnum.UPDATING: + return ODS_BADGE_COLOR.information; + case DiagnosticStatusEnum.TO_CONFIGURE: + case DiagnosticStatusEnum.DISABLED: + default: + return ODS_BADGE_COLOR.neutral; + } +}; + +type StatusBadgeProps = StyleReactProps & + Omit & { + status: DiagnosticStatusEnum; + }; + +const StatusBadge = ({ status, ...props }: StatusBadgeProps) => { + const { t } = useTranslation('domains/diagnostic'); + return ( + + ); +}; + +const TabTitle = ({ + title, + hasError, +}: { + title: string; + hasError?: boolean; +}) => { + return ( + <> + {title} + {hasError && ( + + )} + + ); +}; + +const getDnsRecordConfigurationHelp = ({ + diagnostic, // pass expectedDNSConfig instead + recordType, +}: { + diagnostic: DnsDiagnostic; + recordType: DnsRecordType; +}) => { + const { t } = useTranslation('domains/diagnostic'); + switch (recordType) { + case DnsRecordType.MX: + return ( + <> + + + + {t('zimbra_domain_diagnostic_field_host')} + To be defined + + + + {mxFields.map(({ priority, target }) => ( + + + + + {t('zimbra_domain_diagnostic_field_priority')}{' '} + + {priority} + + + + + {t('zimbra_domain_diagnostic_field_target')} + + + + + ))} + + ); + case DnsRecordType.SPF: + case DnsRecordType.SRV: + return Object.entries( + recordType === DnsRecordType.SRV ? srvFields : spfFields, + ).map(([key, value]) => ( + + + + {t(`zimbra_domain_diagnostic_field_${key}`)} + + + + + )); + case DnsRecordType.DKIM: + return dkimFields.map((cname, index) => ( + + + + + {`CNAME ${index + 1}`} + + + + {Object.entries(cname).map(([key, value]) => ( + + + + {t(`zimbra_domain_diagnostic_field_${key}`)} + + + + + ))} + + )); + default: + return <>; + } +}; + +const TabContent = ({ + diagnostic, + recordType, + guide = GUIDES_LIST.dns_configuration_guide, + isLoading, +}: { + diagnostic: DnsDiagnostic; + recordType: DnsRecordType; + guide?: Guide; + isLoading?: boolean; +}) => { + const { t } = useTranslation('domains/diagnostic'); + const isOvh = false; + const hasError = diagnosticHasError(diagnostic); + const fieldHelpers = getDnsRecordConfigurationHelp({ + diagnostic, + recordType, + }); + + if (isLoading) { + return ; + } + + if (!diagnostic) { + return ( + + Unsupported record type + + ); + } + + return ( +
+ + {t('zimbra_domain_diagnostic_status')} + + + {hasError && diagnostic?.errorMessage && ( + + {diagnostic.errorMessage} + + )} + + {t( + `zimbra_domain_diagnostic_information_message_${recordType.toLowerCase()}_${diagnostic.status.toLowerCase()}`, + { + errorCode: diagnostic.errorCode, + }, + )} + + {hasError && guide && ( + + )} + {hasError && !isOvh && ( + + + + + + {fieldHelpers} + +
+ + {t('zimbra_domain_diagnostic_type')} + + {recordType === DnsRecordType.DKIM ? 'TXT' : recordType} + + +
+ )} + {hasError && isOvh && } +
+ ); +}; + +export default function DomainDiagnostics() { + const { t } = useTranslation('domains/diagnostic'); + const { platformId } = usePlatform(); + const location = useLocation(); + const [searchParams] = useSearchParams(); + const params = Object.fromEntries(searchParams.entries()); + const domainId = searchParams.get('domainId'); + const navigate = useNavigate(); + const goBackUrl = useGenerateUrl('../..', 'href'); + // const onClose = () => navigate(goBackUrl); + + const { + data: domain, + isPending, + isRefetching, + isError, + refetch, + } = useDomainDiagnostic({ + domainId, + }); + + const tabsList: TabItemProps[] = [ + { + name: DnsRecordType.MX, + title: ( + + ), + to: useGenerateUrl('../diagnostics/mx', 'path', params), + pathMatchers: computePathMatchers( + [urls.domains_diagnostic_mx], + platformId, + ), + component: ( + + ), + }, + { + name: DnsRecordType.SRV, + title: , + to: useGenerateUrl('../diagnostics/srv', 'path', params), + isDisabled: true, + pathMatchers: computePathMatchers( + [urls.domains_diagnostic_srv], + platformId, + ), + component: ( + + ), + }, + { + name: DnsRecordType.SPF, + title: ( + + ), + to: useGenerateUrl('../diagnostics/spf', 'path', params), + pathMatchers: computePathMatchers( + [urls.domains_diagnostic_spf], + platformId, + ), + component: ( + + ), + }, + { + name: DnsRecordType.DKIM, + title: ( + + ), + to: useGenerateUrl('../diagnostics/dkim', 'path', params), + pathMatchers: computePathMatchers( + [urls.domains_diagnostic_dkim], + platformId, + ), + component: ( + + ), + }, + ]; + + const currentTab = useMemo( + () => + tabsList.find((tab) => activatedTabs(tab.pathMatchers, location)) + ?.component || tabsList[0].component, + [location, domain, isRefetching], + ); + + const handleRefreshClick = () => { + refetch(); + }; + + if (isError) { + return ( + + Could not fetch diagnostics + + ); + } + + if (isPending || !domain) { + return ; + } + + return ( +
+ + + {t('zimbra_domain_diagnostic_subtitle', { + domain: domain?.domainName, + })} + + +
+ +
+ {currentTab} +
+ ); +} diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Domains.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Domains.tsx index 1565a766ccee..4f2bc9115b45 100644 --- a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Domains.tsx +++ b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/Domains.tsx @@ -26,11 +26,9 @@ import { IAM_ACTIONS } from '@/utils/iamAction.constants'; import { DATAGRID_REFRESH_INTERVAL, DATAGRID_REFRESH_ON_MOUNT, - DnsRecordType, FEATURE_FLAGS, } from '@/utils'; import Loading from '@/components/Loading/Loading'; -import { DiagnosticBadge } from '@/components/DiagnosticBadge'; import { DomainType } from '@/api/domain/type'; import { AccountStatistics, ResourceStatus } from '@/api/api.type'; import { BadgeStatus } from '@/components/BadgeStatus'; @@ -66,36 +64,6 @@ const columns: DatagridColumn[] = [ ), label: 'zimbra_domains_datagrid_account_number', }, - { - id: 'diagnostic', - cell: (item) => { - return ( -
- - - - -
- ); - }, - label: 'zimbra_domains_datagrid_diagnostic_label', - }, { id: 'status', cell: (item) => , @@ -190,18 +158,10 @@ export default function Domains() { ) : ( <> - !( - !FEATURE_FLAGS.DOMAIN_DIAGNOSTICS && - c.id === 'diagnostic' - ), - ) - .map((column) => ({ - ...column, - label: t(column.label), - }))} + columns={columns.map((column) => ({ + ...column, + label: t(column.label), + }))} items={items} totalItems={items.length} hasNextPage={!isFetchingNextPage && hasNextPage} diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalDiagnosticDnsRecord.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalDiagnosticDnsRecord.component.tsx index 92c6f4d1849f..23bd62dc4723 100644 --- a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalDiagnosticDnsRecord.component.tsx +++ b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalDiagnosticDnsRecord.component.tsx @@ -80,7 +80,7 @@ export default function ModalDiagnosticDnsRecord( ); const [domain, setDomain] = useState(); - const { data, isLoading } = useDomain(domainId); + const { data, isLoading } = useDomain({ domainId }); useEffect(() => { setDomain(data); diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalEditDomain.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalEditDomain.component.tsx index 9aefa63b7ea4..b6a8e11f61cc 100644 --- a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalEditDomain.component.tsx +++ b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/ModalEditDomain.component.tsx @@ -40,9 +40,9 @@ export default function ModalEditDomain() { const { platformId } = usePlatform(); - const { data: detailDomain, isLoading: isLoadingDomain } = useDomain( - editDomainId, - ); + const { data: detailDomain, isLoading: isLoadingDomain } = useDomain({ + domainId: editDomainId, + }); const { data: organizationsList, isLoading: isLoadingOrganizations, diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/__test__/ModalsDiagnosticDNSRecord.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Domains/__test__/ModalsDiagnosticDNSRecord.spec.tsx deleted file mode 100644 index 0aba630ae253..000000000000 --- a/packages/manager/apps/zimbra/src/pages/dashboard/Domains/__test__/ModalsDiagnosticDNSRecord.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import 'element-internals-polyfill'; -import '@testing-library/jest-dom'; -import { vi, describe, expect } from 'vitest'; -import { useSearchParams } from 'react-router-dom'; -import { render } from '@/utils/test.provider'; -import { DnsRecordType } from '@/utils'; -import { domainDetailMock } from '@/api/_mock_'; -import ModalDiagnosticDnsRecord from '../ModalDiagnosticDnsRecord.component'; -import domainDiagnosticTranslation from '@/public/translations/domains/diagnostic/Messages_fr_FR.json'; - -vi.mocked(useSearchParams).mockReturnValue([ - new URLSearchParams({ - domainId: domainDetailMock.id, - }), - vi.fn(), -]); - -describe('Domain diagnostic modalc ', () => { - it('should display diagnostic modal', async () => { - const { findByText, getByTestId } = render( - , - ); - - expect( - await findByText( - domainDiagnosticTranslation.zimbra_domain_modal_diagnostic_srv_title, - ), - ).toBeVisible(); - - expect( - getByTestId('diagnostic-srv-modal-secondary-btn'), - ).toBeInTheDocument(); - }); -}); - -describe('Domain diagnostic modal MX', () => { - it('should display diagnostic modal', async () => { - const { findByText, getByTestId } = render( - , - ); - - expect( - await findByText( - domainDiagnosticTranslation.zimbra_domain_modal_diagnostic_mx_title, - ), - ).toBeVisible(); - - expect( - getByTestId('diagnostic-mx-modal-secondary-btn'), - ).toBeInTheDocument(); - }); -}); - -describe('Domain diagnostic modal SPF', () => { - it('should display diagnostic modal', async () => { - const { findByText, getByTestId } = render( - , - ); - - expect( - await findByText( - domainDiagnosticTranslation.zimbra_domain_modal_diagnostic_spf_title, - ), - ).toBeVisible(); - - expect( - getByTestId('diagnostic-spf-modal-secondary-btn'), - ).toBeInTheDocument(); - }); -}); diff --git a/packages/manager/apps/zimbra/src/routes/routes.constants.ts b/packages/manager/apps/zimbra/src/routes/routes.constants.ts index 336e40dadef8..eedbc22dfb28 100644 --- a/packages/manager/apps/zimbra/src/routes/routes.constants.ts +++ b/packages/manager/apps/zimbra/src/routes/routes.constants.ts @@ -8,7 +8,10 @@ export const urls = { domains: '/:serviceName/domains', domainsEdit: '/:serviceName/domains/edit', domainsDelete: '/:serviceName/domains/delete', - domains_diagnostic: '/:serviceName/domains/diagnostic', + domains_diagnostic_mx: '/:serviceName/domains/diagnostics/mx', + domains_diagnostic_srv: '/:serviceName/domains/diagnostics/srv', + domains_diagnostic_spf: '/:serviceName/domains/diagnostics/spf', + domains_diagnostic_dkim: '/:serviceName/domains/diagnostics/dkim', email_accounts: '/:serviceName/email_accounts', email_accounts_add: '/:serviceName/email_accounts/add', email_accounts_edit: '/:serviceName/email_accounts/settings', diff --git a/packages/manager/apps/zimbra/src/routes/routes.tsx b/packages/manager/apps/zimbra/src/routes/routes.tsx index 89880b6aea43..d4835d7b47eb 100644 --- a/packages/manager/apps/zimbra/src/routes/routes.tsx +++ b/packages/manager/apps/zimbra/src/routes/routes.tsx @@ -118,6 +118,46 @@ export const Routes: any = [ breadcrumbLabel: 'zimbra_dashboard_domains_delete', }, }, + { + path: 'diagnostics/mx', + ...lazyRouteConfig(() => + import('@/pages/dashboard/Domains/Diagnostics.page'), + ), + handle: { + isOverridePage: true, + breadcrumbLabel: 'zimbra_dashboard_domains_diagnostics', + }, + }, + { + path: 'diagnostics/srv', + ...lazyRouteConfig(() => + import('@/pages/dashboard/Domains/Diagnostics.page'), + ), + handle: { + isOverridePage: true, + breadcrumbLabel: 'zimbra_dashboard_domains_diagnostics', + }, + }, + { + path: 'diagnostics/spf', + ...lazyRouteConfig(() => + import('@/pages/dashboard/Domains/Diagnostics.page'), + ), + handle: { + isOverridePage: true, + breadcrumbLabel: 'zimbra_dashboard_domains_diagnostics', + }, + }, + { + path: 'diagnostics/dkim', + ...lazyRouteConfig(() => + import('@/pages/dashboard/Domains/Diagnostics.page'), + ), + handle: { + isOverridePage: true, + breadcrumbLabel: 'zimbra_dashboard_domains_diagnostics', + }, + }, ], }, { diff --git a/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts b/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts index c31fc82f84e1..d43513fb05d1 100644 --- a/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts +++ b/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts @@ -20,6 +20,7 @@ export const IAM_ACTIONS = { create: `${IAM_ACTIONS_PREFIX}domain/create`, delete: `${IAM_ACTIONS_PREFIX}domain/delete`, edit: `${IAM_ACTIONS_PREFIX}domain/edit`, + diagnose: `${IAM_ACTIONS_PREFIX}domain/diagnose`, }, mailingList: { create: `${IAM_ACTIONS_PREFIX}mailingList/create`,