diff --git a/src/App/Page/Zone.purs b/src/App/Page/Zone.purs index 9e35f5f..833fa93 100644 --- a/src/App/Page/Zone.purs +++ b/src/App/Page/Zone.purs @@ -513,9 +513,8 @@ render state -- Nothing -> Bulma.p "default value for the version (spf1)" -- Just v -> Bulma.box_input "vSPF" "Version" "spf1" (updateForm Field_SPF_v) v , Bulma.hr - , Bulma.box - [ Bulma.h3 "Current mechanisms" - , maybe (Bulma.p "You don't have any mechanism.") display_mechanisms state._currentRR.mechanisms + , Bulma.box_with_tag C.has_background_info_light tag_mechanisms + [ maybe (Bulma.p "You don't have any mechanism.") display_mechanisms state._currentRR.mechanisms , Bulma.h3 "New mechanism" , Bulma.selection_field "idMechanismQ" "Policy" SPF_Mechanism_q qualifier_types state.spf_mechanism_q , Bulma.selection_field "idMechanismT" "Type" SPF_Mechanism_t mechanism_types state.spf_mechanism_t @@ -525,9 +524,8 @@ render state , Bulma.btn "Add a mechanism" SPF_Mechanism_Add ] , Bulma.hr - , Bulma.box - [ Bulma.h3 "Current modifiers" - , maybe (Bulma.p "You don't have any modifier.") display_modifiers state._currentRR.modifiers + , Bulma.box_with_tag C.has_background_success_light tag_modifiers + [ maybe (Bulma.p "You don't have any modifier.") display_modifiers state._currentRR.modifiers , Bulma.h3 "New modifier" , Bulma.selection_field "idModifierT" "Modifier" SPF_Modifier_t modifier_types state.spf_modifier_t , Bulma.box_input "valueNewModifierSPF" "Value" "" @@ -542,6 +540,13 @@ render state , Bulma.selection SPF_Qualifier qualifier_types (maybe default_qualifier_str show_qualifier state._currentRR.q) ] ] + + tag_mechanisms = tags [tag "Mechanisms"] + tag_modifiers = tags [tag "Modifiers"] + + tag_aggregated_reports = tags [tag "Addresses to contact for aggregated reports"] + tag_detailed_reports = tags [tag "Addresses to contact for detailed reports"] + modal_content_dkim :: Array (HH.HTML w Action) modal_content_dkim = [ Bulma.div_content [] [Bulma.explanation Explanations.dkim_introduction] @@ -608,10 +613,16 @@ render state , Bulma.hr , Bulma.div_content [] [Bulma.explanation Explanations.dmarc_contact] - , maybe (Bulma.p "There is no address to send aggregated reports to.") - (display_dmarc_mail_addresses "Addresses to contact for aggregated reports" DMARC_remove_rua) state.dmarc.rua - , maybe (Bulma.p "There is no address to send detailed reports to.") - (display_dmarc_mail_addresses "Addresses to contact for detailed reports" DMARC_remove_ruf) state.dmarc.ruf + , Bulma.box_with_tag C.has_background_info_light tag_aggregated_reports + [ maybe (Bulma.p "There is no address to send aggregated reports to.") + (display_dmarc_mail_addresses DMARC_remove_rua) + state.dmarc.rua + ] + , Bulma.box_with_tag C.has_background_success_light tag_detailed_reports + [ maybe (Bulma.p "There is no address to send detailed reports to.") + (display_dmarc_mail_addresses DMARC_remove_ruf) + state.dmarc.ruf + ] , Bulma.hr , render_dmarc_mail_errors @@ -1029,15 +1040,15 @@ render_resources :: forall w. Array ResourceRecord -> HH.HTML w Action render_resources [] = Bulma.box [Bulma.zone_rr_title "Resource records", Bulma.subtitle "No records for now"] render_resources records = HH.div_ $ - (rr_box tag_soa bg_color_ro Bulma.soa_table_header table_content all_soa_rr) - <> (rr_box tag_basic [] Bulma.simple_table_header table_content_w_seps all_basic_rr) - <> (rr_box tag_mx [] Bulma.mx_table_header table_content all_mx_rr) - <> (rr_box tag_caa [] Bulma.caa_table_header table_content all_caa_rr) - <> (rr_box tag_srv [] Bulma.srv_table_header table_content all_srv_rr) - <> (rr_box tag_spf [] Bulma.spf_table_header table_content all_spf_rr) - <> (rr_box tag_dkim [] Bulma.dkim_table_header table_content all_dkim_rr) - <> (rr_box tag_dmarc [] Bulma.dmarc_table_header table_content all_dmarc_rr) - <> (rr_box tag_basic_ro bg_color_ro Bulma.simple_table_header_ro table_content_w_seps all_basic_ro_rr) + (rr_box bg_color_ro tag_soa Bulma.soa_table_header table_content all_soa_rr) + <> (rr_box [] tag_basic Bulma.simple_table_header table_content_w_seps all_basic_rr) + <> (rr_box [] tag_mx Bulma.mx_table_header table_content all_mx_rr) + <> (rr_box [] tag_caa Bulma.caa_table_header table_content all_caa_rr) + <> (rr_box [] tag_srv Bulma.srv_table_header table_content all_srv_rr) + <> (rr_box [] tag_spf Bulma.spf_table_header table_content all_spf_rr) + <> (rr_box [] tag_dkim Bulma.dkim_table_header table_content all_dkim_rr) + <> (rr_box [] tag_dmarc Bulma.dmarc_table_header table_content all_dmarc_rr) + <> (rr_box bg_color_ro tag_basic_ro Bulma.simple_table_header_ro table_content_w_seps all_basic_ro_rr) where all_basic_rr = A.filter (\rr -> A.elem rr.rrtype baseRecords && not rr.readonly) records all_basic_ro_rr = A.filter (\rr -> A.elem rr.rrtype baseRecords && rr.readonly) records @@ -1060,16 +1071,15 @@ render_resources records tag_dmarc = tags [tag "DMARC"] tag_basic_ro = tags [tag_ro "Basic Resource Records", tag_ro "read only"] - rr_box :: HH.HTML w Action -- box title (type of data) - -> Array HH.ClassName - -> HH.HTML w Action -- table title - -> (Array ResourceRecord -> HH.HTML w Action) - -> Array ResourceRecord - -> Array (HH.HTML w Action) - rr_box title colors header dp rrs = + rr_box :: Array HH.ClassName -- css classes (such as colors) + -> HH.HTML w Action -- box title (type of data) + -> HH.HTML w Action -- table title + -> (Array ResourceRecord -> HH.HTML w Action) + -> Array ResourceRecord + -> Array (HH.HTML w Action) + rr_box colors title header dp rrs = if A.length rrs > 0 - then [ Bulma.box_ (C.no_padding_left <> C.no_padding_top <> colors) - [title, Bulma.table_ (C.margin_left 3) [] [header, dp rrs]] ] + then [ Bulma.box_with_tag colors title [Bulma.table_ (C.margin_left 3) [] [header, dp rrs]] ] else [] --title_col_props = C.is 1 @@ -1093,7 +1103,6 @@ render_resources records "SOA" -> [ HH.td_ [ HH.text rr.name ] , HH.td_ [ HH.text $ show rr.ttl ] - , HH.td_ [ HH.text rr.target ] , HH.td_ [ HH.text $ maybe "" id rr.mname ] , HH.td_ [ HH.text $ maybe "" id rr.rname ] , HH.td_ [ HH.text $ maybe "" show rr.serial ] @@ -1207,8 +1216,7 @@ render_resources records display_mechanisms :: forall w. Array RR.Mechanism -> HH.HTML w Action display_mechanisms [] = Bulma.p "You don't have any mechanism." display_mechanisms ms = - Bulma.box_ C.has_background_warning_light - [ Bulma.table [] [ Bulma.mechanism_table_header, HH.tbody_ $ map render_mechanism_row $ attach_id 0 ms] ] + Bulma.table [] [ Bulma.mechanism_table_header, HH.tbody_ $ map render_mechanism_row $ attach_id 0 ms] where render_mechanism_row :: (Tuple Int RR.Mechanism) -> HH.HTML w Action render_mechanism_row (Tuple i m) = HH.tr_ @@ -1221,8 +1229,7 @@ display_mechanisms ms = display_modifiers :: forall w. Array RR.Modifier -> HH.HTML w Action display_modifiers [] = Bulma.p "You don't have any modifier." display_modifiers ms = - Bulma.box_ C.has_background_warning_light - [ Bulma.table [] [ Bulma.modifier_table_header, HH.tbody_ $ map render_modifier_row $ attach_id 0 ms] ] + Bulma.table [] [ Bulma.modifier_table_header, HH.tbody_ $ map render_modifier_row $ attach_id 0 ms] where render_modifier_row :: (Tuple Int RR.Modifier) -> HH.HTML w Action render_modifier_row (Tuple i m) = HH.tr_ @@ -1231,11 +1238,9 @@ display_modifiers ms = , HH.td_ [ Bulma.alert_btn "x" (SPF_remove_modifier i) ] ] -display_dmarc_mail_addresses :: forall w. String -> (Int -> Action) -> Array DMARC.DMARCURI -> HH.HTML w Action -display_dmarc_mail_addresses t f ms = - Bulma.box_ C.has_background_warning_light - [ Bulma.h3 t - , Bulma.table [] [ Bulma.dmarc_dmarcuri_table_header, HH.tbody_ $ map render_dmarcuri_row $ attach_id 0 ms] ] +display_dmarc_mail_addresses :: forall w. (Int -> Action) -> Array DMARC.DMARCURI -> HH.HTML w Action +display_dmarc_mail_addresses f ms = + Bulma.table [] [ Bulma.dmarc_dmarcuri_table_header, HH.tbody_ $ map render_dmarcuri_row $ attach_id 0 ms] where render_dmarcuri_row :: (Tuple Int DMARC.DMARCURI) -> HH.HTML w Action render_dmarcuri_row (Tuple i m) = HH.tr_ diff --git a/src/Bulma.purs b/src/Bulma.purs index c0815f9..e800a36 100644 --- a/src/Bulma.purs +++ b/src/Bulma.purs @@ -105,14 +105,109 @@ dmarc_dmarcuri_table_header ] ] +name_header :: forall w i. HH.HTML w i +name_header = HH.abbr + [ HP.title "Name of the DNS entry, the fully-qualified-domain-name is .." ] + [ HH.text "Name" ] + +ttl_header :: forall w i. HH.HTML w i +ttl_header = HH.abbr + [ HP.title "Time-to-Live, nb seconds before being considered invalid" ] + [ HH.text "TTL" ] + +target_header :: forall w i. HH.HTML w i +target_header = HH.abbr + [ HP.title "In the DNS jargon, the target means the most important value associated with the entry, for an A entry it would be an IPv4 address, for example" ] + [ HH.text "Target" ] + +token_header :: forall w i. HH.HTML w i +token_header = HH.abbr + [ HP.title "Tokens are used to update the entry, see the tab: \"Tokens? 🤨\"" ] + [ HH.text "Token" ] + +priority_header :: forall w i. HH.HTML w i +priority_header = HH.abbr + [ HP.title "A numeric value that indicates the preference of the server (lower values indicate higher priority)" ] + [ HH.text "Priority" ] + +weight_header :: forall w i. HH.HTML w i +weight_header = HH.abbr + [ HP.title "A relative weight used when multiple servers have the same priority, determining how often they should be used" ] + [ HH.text "Weight" ] + +srv_mechanisms_header :: forall w i. HH.HTML w i +srv_mechanisms_header = HH.abbr + [ HP.title "Mechanisms specify which mail servers are allowed to send mail for the domain and how to evaluate the sending mail server’s IP address" ] + [ HH.text "Mechanisms" ] + +srv_modifiers_header :: forall w i. HH.HTML w i +srv_modifiers_header = HH.abbr + [ HP.title "Modifiers provide additional instructions, such as explanations for SPF failures or redirecting SPF checks to another domain" ] + [ HH.text "Modifiers" ] + +srv_default_policy_header :: forall w i. HH.HTML w i +srv_default_policy_header = HH.abbr + [ HP.title "" ] + [ HH.text "Default Policy" ] + +protocol_header :: forall w i. HH.HTML w i +protocol_header = HH.abbr + [ HP.title "The related communication protocol, either TCP or UDP (want more? Just ask me)" ] + [ HH.text "Protocol" ] + +port_header :: forall w i. HH.HTML w i +port_header = HH.abbr + [ HP.title "Related connection port" ] + [ HH.text "Port" ] + +dkim_notes_header :: forall w i. HH.HTML w i +dkim_notes_header = HH.abbr + [ HP.title "Arbitrary string related to this cryptographic material" ] + [ HH.text "Notes" ] + +dmarc_policy_header :: forall w i. HH.HTML w i +dmarc_policy_header = HH.abbr + [ HP.title "How to handle email when SPF and DKIM aren't valid?" ] + [ HH.text "Policy" ] + +dmarc_subdom_policy_header :: forall w i. HH.HTML w i +dmarc_subdom_policy_header = HH.abbr + [ HP.title "How to handle email when SPF and DKIM aren't valid?" ] + [ HH.text "Subdomain Policy" ] + +dmarc_dkim_policy_header :: forall w i. HH.HTML w i +dmarc_dkim_policy_header = HH.abbr + [ HP.title "What should be considered acceptable to do with an email not conforming with DKIM" ] + [ HH.text "DKIM Policy" ] + +dmarc_spf_policy_header :: forall w i. HH.HTML w i +dmarc_spf_policy_header = HH.abbr + [ HP.title "What should be considered acceptable to do with an email not conforming with SPF" ] + [ HH.text "SPF Policy" ] + +dmarc_sample_rate_header :: forall w i. HH.HTML w i +dmarc_sample_rate_header = HH.abbr + [ HP.title "Percentage of messages subjected to the requested policy [0-100]" ] + [ HH.text "Sample Rate" ] + +dmarc_report_on_header :: forall w i. HH.HTML w i +dmarc_report_on_header = HH.abbr + [ HP.title "What error should be reported? DKIM, SPF, Both, Any or None?" ] + [ HH.text "Report on" ] + +dmarc_report_interval_header :: forall w i. HH.HTML w i +dmarc_report_interval_header = HH.abbr + [ HP.title "Minimal duration between two DMARC reports (in seconds)" ] + [ HH.text "Report interval" ] + simple_table_header :: forall w i. HH.HTML w i simple_table_header = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Type" ] - , HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] - , HH.th_ [ HH.text "Target" ] + , HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] + , HH.th_ [ target_header ] , HH.th_ [ HH.text "" ] - , HH.th_ [ HH.text "Token" ] + , HH.th_ [ token_header ] ] ] @@ -120,27 +215,27 @@ simple_table_header_ro :: forall w i. HH.HTML w i simple_table_header_ro = HH.thead_ [ HH.tr [ HP.classes C.has_background_warning_light ] [ HH.th [ HP.style "width: 50px;" ] [ HH.text "Type" ] - , HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] - , HH.th_ [ HH.text "Target" ] + , HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] + , HH.th_ [ target_header ] , HH.th_ [ HH.text "" ] ] ] mx_table_header :: forall w i. HH.HTML w i mx_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] - , HH.th_ [ HH.text "Priority" ] - , HH.th_ [ HH.text "Target" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] + , HH.th_ [ priority_header ] + , HH.th_ [ target_header ] , HH.th_ [ HH.text "" ] ] ] caa_table_header :: forall w i. HH.HTML w i caa_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] , HH.th_ [ HH.text "Flag" ] , HH.th_ [ HH.text "Tag" ] , HH.th_ [ HH.text "Value" ] @@ -150,73 +245,112 @@ caa_table_header srv_table_header :: forall w i. HH.HTML w i srv_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "Protocol" ] - , HH.th_ [ HH.text "Target" ] - , HH.th_ [ HH.text "Port" ] - , HH.th_ [ HH.text "TTL" ] - , HH.th_ [ HH.text "Priority" ] - , HH.th_ [ HH.text "Weight" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ protocol_header ] + , HH.th_ [ target_header ] + , HH.th_ [ port_header ] + , HH.th_ [ ttl_header ] + , HH.th_ [ priority_header ] + , HH.th_ [ weight_header ] , HH.th_ [ HH.text "" ] ] ] spf_table_header :: forall w i. HH.HTML w i spf_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] -- , HH.th_ [ HH.text "Version" ] -- For now, version isn't displayed. - , HH.th_ [ HH.text "Mechanisms" ] - , HH.th_ [ HH.text "Modifiers" ] - , HH.th_ [ HH.text "Default Policy" ] + , HH.th_ [ srv_mechanisms_header ] + , HH.th_ [ srv_modifiers_header ] + , HH.th_ [ srv_default_policy_header ] , HH.th_ [ HH.text "" ] ] ] dkim_table_header :: forall w i. HH.HTML w i dkim_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] -- , HH.th_ [ HH.text "Version" ] -- For now, version isn't displayed. Assume DKIM1. , HH.th_ [ HH.text "Hash algo" ] , HH.th_ [ HH.text "Signature algo" ] , HH.th_ [ HH.text "Public Key" ] - , HH.th_ [ HH.text "Notes" ] + , HH.th_ [ dkim_notes_header ] , HH.th_ [ HH.text "" ] ] ] dmarc_table_header :: forall w i. HH.HTML w i dmarc_table_header - = HH.thead_ [ HH.tr_ [ HH.th_ [ HH.text "Name" ] - , HH.th_ [ HH.text "TTL" ] + = HH.thead_ [ HH.tr_ [ HH.th_ [ name_header ] + , HH.th_ [ ttl_header ] -- , HH.th_ [ HH.text "Version" ] -- For now, version isn't displayed. Assume DMARC1. - , HH.th_ [ HH.text "Policy" ] -- p - , HH.th_ [ HH.text "Subdomain Policy" ] -- sp - , HH.th_ [ HH.text "DKIM policy" ] -- adkim - , HH.th_ [ HH.text "SPF policy" ] -- aspf - , HH.th_ [ HH.text "Sample rate" ] -- pct - , HH.th_ [ HH.text "Report on" ] -- fo - , HH.th_ [ HH.text "Report interval" ] -- ri + , HH.th_ [ dmarc_policy_header ] -- p + , HH.th_ [ dmarc_subdom_policy_header ] -- sp + , HH.th_ [ dmarc_dkim_policy_header ] -- adkim + , HH.th_ [ dmarc_spf_policy_header ] -- aspf + , HH.th_ [ dmarc_sample_rate_header ] -- pct + , HH.th_ [ dmarc_report_on_header ] -- fo + , HH.th_ [ dmarc_report_interval_header ] -- ri -- TODO? rua & ruf -- , HH.th_ [ HH.text "Accepted report formats" ] -- For now, assume AFRF. , HH.th_ [ HH.text "" ] ] ] +name_soa_header :: forall w i. HH.HTML w i +name_soa_header = HH.abbr + [ HP.title "Your actual domain name (technical term: \"fully qualified domain name\")" ] + [ HH.text "Name" ] + +mname_soa_header :: forall w i. HH.HTML w i +mname_soa_header = HH.abbr + [ HP.title "Domain name of the primary authoritative DNS server for the zone (SOA \"MNAME\" field)" ] + [ HH.text "Primary NS" ] + +rname_soa_header :: forall w i. HH.HTML w i +rname_soa_header = HH.abbr + [ HP.title "The email address of the person responsible for managing the zone (the \"@\" is replaced by \".\" for some reason, this is the SOA \"RNAME\" field)" ] + [ HH.text "Contact" ] + +serial_soa_header :: forall w i. HH.HTML w i +serial_soa_header = HH.abbr + [ HP.title "A number that is incremented every time the zone is updated. Secondary DNS servers use this number to check for updates" ] + [ HH.text "Serial" ] + +refresh_soa_header :: forall w i. HH.HTML w i +refresh_soa_header = HH.abbr + [ HP.title "The interval (in seconds) at which secondary DNS servers should check the primary server for changes to the zone" ] + [ HH.text "Refresh" ] + +retry_soa_header :: forall w i. HH.HTML w i +retry_soa_header = HH.abbr + [ HP.title "The time in seconds that secondary servers should wait before retrying a failed attempt to contact the primary DNS server" ] + [ HH.text "Retry" ] + +expire_soa_header :: forall w i. HH.HTML w i +expire_soa_header = HH.abbr + [ HP.title "The time in seconds that secondary DNS servers will keep the zone data before discarding it if they cannot contact the primary server" ] + [ HH.text "Expire" ] + +minttl_soa_header :: forall w i. HH.HTML w i +minttl_soa_header = HH.abbr + [ HP.title "The minimum time (in seconds) that other DNS servers should cache negative responses (e.g., for non-existent domain names)" ] + [ HH.text "Minimum TTL" ] + soa_table_header :: forall w i. HH.HTML w i soa_table_header = HH.thead_ [ HH.tr [ HP.classes C.has_background_warning_light ] - [ HH.th_ [ HH.text "name"] - , HH.th_ [ HH.text "ttl"] - , HH.th_ [ HH.text "target"] - , HH.th_ [ HH.text "mname"] - , HH.th_ [ HH.text "rname"] - , HH.th_ [ HH.text "serial"] - , HH.th_ [ HH.text "refresh"] - , HH.th_ [ HH.text "retry"] - , HH.th_ [ HH.text "expire"] - , HH.th_ [ HH.text "minttl"] + [ HH.th_ [ name_soa_header ] + , HH.th_ [ ttl_header ] + , HH.th_ [ mname_soa_header ] + , HH.th_ [ rname_soa_header ] + , HH.th_ [ serial_soa_header ] + , HH.th_ [ refresh_soa_header ] + , HH.th_ [ retry_soa_header ] + , HH.th_ [ expire_soa_header ] + , HH.th_ [ minttl_soa_header ] ] ] @@ -683,3 +817,14 @@ btn_validation_ str = HH.button btn_validation :: forall w i. HH.HTML w i btn_validation = btn_validation_ "Validate" + +-- | Box with tags. +box_with_tag :: forall w action. + Array HH.ClassName -- css classes (like the color) + -> HH.HTML w action -- tag (title for the box) + -> Array (HH.HTML w action) -- box content + -> HH.HTML w action +box_with_tag colors tag xs + = box_ + (C.no_padding_left <> C.no_padding_top <> colors) + [tag, HH.div [HP.classes $ C.restore_padding_left <> C.restore_padding_top] xs] diff --git a/src/CSSClasses.purs b/src/CSSClasses.purs index 98176ba..cf8dc54 100644 --- a/src/CSSClasses.purs +++ b/src/CSSClasses.purs @@ -112,6 +112,8 @@ no_padding_left = [HH.ClassName "pl-0"] :: no_padding_top = [HH.ClassName "pt-0"] :: Array HH.ClassName normal = [HH.ClassName "is-normal"] :: Array HH.ClassName notification = [HH.ClassName "notification"] :: Array HH.ClassName +restore_padding_left = [HH.ClassName "pl-4"] :: Array HH.ClassName +restore_padding_top = [HH.ClassName "pt-4"] :: Array HH.ClassName section = [HH.ClassName "section"] :: Array HH.ClassName select = [HH.ClassName "select"] :: Array HH.ClassName subtitle = [HH.ClassName "subtitle"] :: Array HH.ClassName