Bulma: s/Style/Web/ + modals in a module
This commit is contained in:
parent
e36be137db
commit
57e212420c
24 changed files with 1022 additions and 916 deletions
|
@ -45,7 +45,7 @@ module App.Container where
|
|||
|
||||
import Prelude (Unit, bind, discard, unit, ($), (=<<), (<>), show, pure, (+), (&&), (>))
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
import Data.Array as A
|
||||
import Data.ArrayBuffer.Types (ArrayBuffer)
|
||||
|
@ -299,9 +299,9 @@ render state
|
|||
Migration -> render_migration
|
||||
LegalNotice -> render_legal_notice
|
||||
-- The footer includes logs and both the WS child components.
|
||||
, Style.hr
|
||||
, Style.columns_ [ Style.column_ [ Style.h3 "Logs (watch this if something fails 😅)", render_logs ]
|
||||
, Style.column_ [ Style.level
|
||||
, Web.hr
|
||||
, Web.columns_ [ Web.column_ [ Web.h3 "Logs (watch this if something fails 😅)", render_logs ]
|
||||
, Web.column_ [ Web.level
|
||||
[ render_auth_WS
|
||||
, render_dnsmanager_WS
|
||||
, legal_notice_btn
|
||||
|
@ -317,12 +317,12 @@ render state
|
|||
migration_warning :: forall w. HH.HTML w Action
|
||||
migration_warning =
|
||||
HH.div [HP.classes [C.notification, C.is_warning]]
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
⚠️ (FR) le service a été migré d'une ancienne base de code récemment.
|
||||
Le développement se poursuit mais le service devrait être stable, mis à part quelques redémarrages de temps à autre.
|
||||
Merci de nous contacter si vous voyez une erreur.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
⚠️ (EN) migration from old codebase was performed.
|
||||
Development is still on-going but the service should be fairly stable.
|
||||
Reboots will happen on occasion.
|
||||
|
@ -335,23 +335,23 @@ render state
|
|||
case state.user_data of
|
||||
Just (Tuple Nothing _) ->
|
||||
HH.div [HP.classes [C.notification, C.is_warning]]
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
⚠️ MIGRATION (FR): veuillez indiquer une adresse email pour votre compte.
|
||||
Tout compte sans adresse email sera supprimé sous 6 mois.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
⚠️ MIGRATION (EN): please associate an email address to your account.
|
||||
Accounts without a validated email address will be discarded within 6 months.
|
||||
"""
|
||||
]
|
||||
_ -> HH.text ""
|
||||
|
||||
legal_notice_btn = Style.btn_ [] "Legal notice" (Routing LegalNotice)
|
||||
legal_notice_btn = Web.btn_ [] "Legal notice" (Routing LegalNotice)
|
||||
reconnection_bar :: forall w. HH.HTML w Action
|
||||
reconnection_bar =
|
||||
if (state.are_we_connected_to_authd && state.are_we_connected_to_dnsmanagerd)
|
||||
then HH.div_ []
|
||||
else Style.btn_ [C.is_large, C.is_danger] "You have been disconnected. Click here to reconnect." Reconnection
|
||||
else Web.btn_ [C.is_large, C.is_danger] "You have been disconnected. Click here to reconnect." Reconnection
|
||||
render_auth_WS :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_auth_WS = HH.slot _ws_auth unit WS.component (Tuple wsURLauthd "authd") AuthenticationDaemonEvent
|
||||
|
||||
|
@ -361,8 +361,8 @@ render state
|
|||
render_notifications =
|
||||
case state.notif of
|
||||
NoNotification -> HH.div_ []
|
||||
GoodNotification v -> Style.box [Style.notification_success v CloseNotif]
|
||||
BadNotification v -> Style.box [Style.notification_danger v CloseNotif]
|
||||
GoodNotification v -> Web.box [Web.notification_success v CloseNotif]
|
||||
BadNotification v -> Web.box [Web.notification_danger v CloseNotif]
|
||||
|
||||
render_home :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_home = HH.slot_ _ho unit HomeInterface.component unit
|
||||
|
@ -375,7 +375,7 @@ render state
|
|||
render_setup :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_setup = case state.user_data of
|
||||
Just user_data -> HH.slot _setupi unit SetupInterface.component user_data SetupInterfaceEvent
|
||||
Nothing -> Style.p "You shouldn't see this page. Please, reconnect."
|
||||
Nothing -> Web.p "You shouldn't see this page. Please, reconnect."
|
||||
render_mail_validation :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_mail_validation = HH.slot _mvi unit MVI.component unit MailValidationInterfaceEvent
|
||||
render_zone :: forall monad. String -> MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
|
@ -388,8 +388,8 @@ render state
|
|||
|
||||
render_legal_notice :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_legal_notice
|
||||
= Style.section_small [ Explanations.legal_notice
|
||||
, Style.btn_ [C.is_large, C.margin_top 3, C.is_info] "Home page" (Routing Home)
|
||||
= Web.section_small [ Explanations.legal_notice
|
||||
, Web.btn_ [C.is_large, C.margin_top 3, C.is_info] "Home page" (Routing Home)
|
||||
]
|
||||
|
||||
render_nav :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
|
@ -409,7 +409,7 @@ render state
|
|||
]
|
||||
|
||||
render_logs :: forall monad. MonadAff monad => H.ComponentHTML Action ChildSlots monad
|
||||
render_logs = Style.container [ HH.slot_ _log unit AppLog.component unit ]
|
||||
render_logs = Web.container [ HH.slot_ _log unit AppLog.component unit ]
|
||||
|
||||
ref_paypal_div :: H.RefLabel
|
||||
ref_paypal_div = H.RefLabel "paypal-div"
|
||||
|
|
|
@ -15,37 +15,37 @@ import App.Validation.Label as ValidationLabel
|
|||
import GenericParser.DomainParser.Common (DomainError(..)) as DomainParser
|
||||
import GenericParser.IPAddress as IPAddress
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
error_to_paragraph :: forall w i. ValidationDNS.Error -> HH.HTML w i
|
||||
error_to_paragraph v = Style.error_message (Style.p $ show_error_title v)
|
||||
error_to_paragraph v = Web.error_message (Web.p $ show_error_title v)
|
||||
(case v of
|
||||
ValidationDNS.UNKNOWN -> Style.p "An internal error happened."
|
||||
ValidationDNS.UNKNOWN -> Web.p "An internal error happened."
|
||||
ValidationDNS.VEIPv4 err -> maybe default_error show_error_ip4 err.error
|
||||
ValidationDNS.VEIPv6 err -> maybe default_error show_error_ip6 err.error
|
||||
ValidationDNS.VEName err -> maybe default_error show_error_domain err.error
|
||||
ValidationDNS.VETTL min max n ->
|
||||
Style.p $ "TTL should have a value between "
|
||||
Web.p $ "TTL should have a value between "
|
||||
<> show min <> " and " <> show max <> ", current value: " <> show n <> "."
|
||||
ValidationDNS.VEDMARCpct min max n ->
|
||||
Style.p $ "DMARC sample rate should have a value between "
|
||||
Web.p $ "DMARC sample rate should have a value between "
|
||||
<> show min <> " and " <> show max <> ", current value: " <> show n <> "."
|
||||
ValidationDNS.VEDMARCri min max n ->
|
||||
Style.p $ "DMARC report interval should have a value between "
|
||||
Web.p $ "DMARC report interval should have a value between "
|
||||
<> show min <> " and " <> show max <> ", current value: " <> show n <> "."
|
||||
ValidationDNS.VETXT err -> maybe default_error show_error_txt err.error
|
||||
ValidationDNS.VECNAME err -> maybe default_error show_error_domain err.error
|
||||
ValidationDNS.VENS err -> maybe default_error show_error_domain err.error
|
||||
ValidationDNS.VEMX err -> maybe default_error show_error_domain err.error
|
||||
ValidationDNS.VEPriority min max n -> Style.p $ "Priority should have a value between " <> show min <> " and " <> show max
|
||||
ValidationDNS.VEPriority min max n -> Web.p $ "Priority should have a value between " <> show min <> " and " <> show max
|
||||
<> ", current value: " <> show n <> "."
|
||||
ValidationDNS.VESRV err -> maybe default_error show_error_domain err.error
|
||||
ValidationDNS.VEPort min max n -> Style.p $ "Port should have a value between " <> show min <> " and " <> show max
|
||||
ValidationDNS.VEPort min max n -> Web.p $ "Port should have a value between " <> show min <> " and " <> show max
|
||||
<> ", current value: " <> show n <> "."
|
||||
ValidationDNS.VEWeight min max n -> Style.p $ "Weight should have a value between " <> show min <> " and " <> show max
|
||||
ValidationDNS.VEWeight min max n -> Web.p $ "Weight should have a value between " <> show min <> " and " <> show max
|
||||
<> ", current value: " <> show n <> "."
|
||||
|
||||
ValidationDNS.VECAAflag min max n -> Style.p $ "CAA flag should have a value between " <> show min <> " and " <> show max
|
||||
ValidationDNS.VECAAflag min max n -> Web.p $ "CAA flag should have a value between " <> show min <> " and " <> show max
|
||||
<> ", current value: " <> show n <> "."
|
||||
|
||||
-- SPF dedicated RR
|
||||
|
@ -56,13 +56,13 @@ error_to_paragraph v = Style.error_message (Style.p $ show_error_title v)
|
|||
|
||||
ValidationDNS.DKIMInvalidKeySize min max -> show_error_key_sizes min max
|
||||
)
|
||||
where default_error = Style.p ""
|
||||
where default_error = Web.p ""
|
||||
|
||||
show_error_key_sizes :: forall w i. Int -> Int -> HH.HTML w i
|
||||
show_error_key_sizes min max
|
||||
= if min == max
|
||||
then Style.p $ "Chosen signature algorithm only accepts public key input of " <> show min <> " characters."
|
||||
else Style.p $ "Chosen signature algorithm only accepts public key input between "
|
||||
then Web.p $ "Chosen signature algorithm only accepts public key input of " <> show min <> " characters."
|
||||
else Web.p $ "Chosen signature algorithm only accepts public key input between "
|
||||
<> show min <> " and " <> show max <> " characters."
|
||||
|
||||
-- | `show_error_title` provide a simple title string to display to the user in case of an error with an entry.
|
||||
|
@ -96,12 +96,12 @@ show_error_title v = case v of
|
|||
show_error_domain :: forall w i. DomainParser.DomainError -> HH.HTML w i
|
||||
show_error_domain e = case e of
|
||||
DomainParser.LabelTooLarge size ->
|
||||
Style.p $ "The label contains too many characters (" <> show size <> ")."
|
||||
Web.p $ "The label contains too many characters (" <> show size <> ")."
|
||||
DomainParser.DomainTooLarge size ->
|
||||
Style.p $ "The domain contains too many characters (" <> show size <> ")."
|
||||
Web.p $ "The domain contains too many characters (" <> show size <> ")."
|
||||
-- DomainParser.InvalidCharacter
|
||||
-- DomainParser.EOFExpected
|
||||
_ -> Style.p """
|
||||
_ -> Web.p """
|
||||
The domain (or label) contains invalid characters.
|
||||
A domain label should start with a letter,
|
||||
then possibly a series of letters, digits and hyphenations ("-"),
|
||||
|
@ -111,31 +111,31 @@ show_error_domain e = case e of
|
|||
show_error_ip6 :: forall w i. IPAddress.IPv6Error -> HH.HTML w i
|
||||
show_error_ip6 e = case e of
|
||||
IPAddress.IP6TooManyHexaDecimalCharacters ->
|
||||
Style.p "IP6TooManyHexaDecimalCharacters"
|
||||
Web.p "IP6TooManyHexaDecimalCharacters"
|
||||
IPAddress.IP6NotEnoughChunks ->
|
||||
Style.p """
|
||||
Web.p """
|
||||
The IPv6 representation is erroneous, it should contain 8 groups of hexadecimal characters or
|
||||
being shortened with a double ':' character, such as "2000::1".
|
||||
"""
|
||||
IPAddress.IP6TooManyChunks ->
|
||||
Style.p "The IPv6 representation is erroneous. It should contain only up to 8 groups of hexadecimal characters."
|
||||
Web.p "The IPv6 representation is erroneous. It should contain only up to 8 groups of hexadecimal characters."
|
||||
IPAddress.IP6IrrelevantShortRepresentation ->
|
||||
Style.p "IPv6 address has been unnecessarily shortened (with two ':')."
|
||||
IPAddress.IP6InvalidRange -> Style.p "IPv6 address or range isn't valid."
|
||||
Web.p "IPv6 address has been unnecessarily shortened (with two ':')."
|
||||
IPAddress.IP6InvalidRange -> Web.p "IPv6 address or range isn't valid."
|
||||
|
||||
show_error_ip4 :: forall w i. IPAddress.IPv4Error -> HH.HTML w i
|
||||
show_error_ip4 e = case e of
|
||||
IPAddress.IP4NumberTooBig n ->
|
||||
Style.p $ "IPv4 address contains a number too big (should be between 0 and 255). Current entered number: " <> show n
|
||||
Web.p $ "IPv4 address contains a number too big (should be between 0 and 255). Current entered number: " <> show n
|
||||
IPAddress.IP4IrrelevantShortRepresentation ->
|
||||
Style.p "IPv4 address has been unnecessarily shortened (with two '.')."
|
||||
IPAddress.IP4InvalidRange -> Style.p "IPv4 address or range isn't valid."
|
||||
Web.p "IPv4 address has been unnecessarily shortened (with two '.')."
|
||||
IPAddress.IP4InvalidRange -> Web.p "IPv4 address or range isn't valid."
|
||||
|
||||
show_error_txt :: forall w i. ValidationDNS.TXTError -> HH.HTML w i
|
||||
show_error_txt e = case e of
|
||||
ValidationDNS.TXTInvalidCharacter -> Style.p "The TXT field contains some invalid characters."
|
||||
ValidationDNS.TXTInvalidCharacter -> Web.p "The TXT field contains some invalid characters."
|
||||
ValidationDNS.TXTTooLong max n ->
|
||||
Style.p $ "An TXT field is limited to " <> show max <> " characters (currently there are "
|
||||
Web.p $ "An TXT field is limited to " <> show max <> " characters (currently there are "
|
||||
<> show n <> " characters)."
|
||||
|
||||
domainerror_string :: DomainParser.DomainError -> String
|
||||
|
@ -146,14 +146,14 @@ domainerror_string (DomainParser.EOFExpected) = "EOFExpected"
|
|||
|
||||
-- | This `error_to_paragraph` is designed to go along the `Validation.Label` module.
|
||||
error_to_paragraph_label :: forall w i. ValidationLabel.Error -> HH.HTML w i
|
||||
error_to_paragraph_label v = Style.error_message (Style.p $ show_error_title_label v)
|
||||
error_to_paragraph_label v = Web.error_message (Web.p $ show_error_title_label v)
|
||||
(case v of
|
||||
ValidationLabel.ParsingError x -> case x.error of
|
||||
Nothing -> Style.p ""
|
||||
Nothing -> Web.p ""
|
||||
Just (ValidationLabel.CannotParse err) -> show_error_domain err
|
||||
Just (ValidationLabel.CannotEntirelyParse) -> Style.p "Cannot entirely parse the label."
|
||||
Just (ValidationLabel.CannotEntirelyParse) -> Web.p "Cannot entirely parse the label."
|
||||
Just (ValidationLabel.Size min max n) ->
|
||||
Style.p $ "Label size should be between " <> show min <> " and " <> show max
|
||||
Web.p $ "Label size should be between " <> show min <> " and " <> show max
|
||||
<> " (current size: " <> show n <> ")."
|
||||
)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ module App.Page.Administration where
|
|||
import Prelude (Unit, bind, discard, not, pure, show, ($), (<<<), (<>), (=<<), map, (/=), (==), unit)
|
||||
import Data.Eq (class Eq)
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Data.Array as A
|
||||
|
@ -128,64 +128,64 @@ initialState _ = { addUserForm: { login: "", admin: false, email: "", pass: "
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { addUserForm, searchUserForm, matching_users, current_tab, orphan_domains }
|
||||
= Style.section_small
|
||||
= Web.section_small
|
||||
[ fancy_tab_bar
|
||||
, case current_tab of
|
||||
Home -> Style.h3 "Select an action"
|
||||
Search -> Style.columns_
|
||||
[ Style.column [C.is 3] [Style.article (Style.p "Search users") render_searchuser_form]
|
||||
, Style.column_ [ Style.h3 "Result", show_found_users ]
|
||||
Home -> Web.h3 "Select an action"
|
||||
Search -> Web.columns_
|
||||
[ Web.column [C.is 3] [Web.article (Web.p "Search users") render_searchuser_form]
|
||||
, Web.column_ [ Web.h3 "Result", show_found_users ]
|
||||
]
|
||||
Add -> Style.columns_
|
||||
[ Style.column [C.is 5] [Style.article (Style.p "Add a new user") render_adduser_form] ]
|
||||
Add -> Web.columns_
|
||||
[ Web.column [C.is 5] [Web.article (Web.p "Add a new user") render_adduser_form] ]
|
||||
OrphanDomains -> HH.div_
|
||||
[ Style.btn_ [C.is_small] "Get orphan domains" ShowOrphanDomains
|
||||
[ Web.btn_ [C.is_small] "Get orphan domains" ShowOrphanDomains
|
||||
, show_orphan_domains
|
||||
]
|
||||
]
|
||||
where
|
||||
fancy_tab_bar =
|
||||
Style.fancy_tabs
|
||||
[ Style.tab_entry (is_tab_active Home) "Home" (ChangeTab Home)
|
||||
, Style.tab_entry (is_tab_active Search) "Search" (ChangeTab Search)
|
||||
, Style.tab_entry (is_tab_active Add) "Add" (ChangeTab Add)
|
||||
, Style.tab_entry (is_tab_active OrphanDomains) "OrphanDomains" (ChangeTab OrphanDomains)
|
||||
Web.fancy_tabs
|
||||
[ Web.tab_entry (is_tab_active Home) "Home" (ChangeTab Home)
|
||||
, Web.tab_entry (is_tab_active Search) "Search" (ChangeTab Search)
|
||||
, Web.tab_entry (is_tab_active Add) "Add" (ChangeTab Add)
|
||||
, Web.tab_entry (is_tab_active OrphanDomains) "OrphanDomains" (ChangeTab OrphanDomains)
|
||||
]
|
||||
is_tab_active tab = current_tab == tab
|
||||
|
||||
show_found_users = Style.box [ HH.ul_ $ map user_card matching_users ]
|
||||
user_card user = HH.li_ [ Style.btn_delete (RemoveUser user.uid)
|
||||
, Style.btn_ [C.is_small] user.login (ShowUser user.uid)
|
||||
show_found_users = Web.box [ HH.ul_ $ map user_card matching_users ]
|
||||
user_card user = HH.li_ [ Web.btn_delete (RemoveUser user.uid)
|
||||
, Web.btn_ [C.is_small] user.login (ShowUser user.uid)
|
||||
]
|
||||
show_orphan_domains = Style.box [ HH.ul_ $ map domain_entry orphan_domains ]
|
||||
domain_entry domain = HH.li_ [ Style.btn_delete (RemoveDomain domain)
|
||||
, Style.btn_ [C.is_small] domain (ShowDomain domain)
|
||||
show_orphan_domains = Web.box [ HH.ul_ $ map domain_entry orphan_domains ]
|
||||
domain_entry domain = HH.li_ [ Web.btn_delete (RemoveDomain domain)
|
||||
, Web.btn_ [C.is_small] domain (ShowDomain domain)
|
||||
]
|
||||
up x = HandleAddUserInput <<< x
|
||||
|
||||
render_adduser_form =
|
||||
HH.form
|
||||
[ HE.onSubmit PreventSubmit ]
|
||||
[ Style.box_input "login" "User login" "login" (up ADDUSER_INP_login) addUserForm.login
|
||||
, Style.btn_labeled "adminbtn" "Admin" (show addUserForm.admin) (HandleAddUserInput ADDUSER_toggle_admin)
|
||||
, Style.box_input "email" "User email" "email" (up ADDUSER_INP_email) addUserForm.email
|
||||
, Style.box_password "password" "User password" "password" (up ADDUSER_INP_pass) addUserForm.pass
|
||||
, Style.btn "Send" AddUserAttempt
|
||||
[ Web.box_input "login" "User login" "login" (up ADDUSER_INP_login) addUserForm.login
|
||||
, Web.btn_labeled "adminbtn" "Admin" (show addUserForm.admin) (HandleAddUserInput ADDUSER_toggle_admin)
|
||||
, Web.box_input "email" "User email" "email" (up ADDUSER_INP_email) addUserForm.email
|
||||
, Web.box_password "password" "User password" "password" (up ADDUSER_INP_pass) addUserForm.pass
|
||||
, Web.btn "Send" AddUserAttempt
|
||||
]
|
||||
|
||||
render_searchuser_form =
|
||||
HH.form
|
||||
[ HE.onSubmit PreventSubmit ]
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Following input accepts any regex.
|
||||
This is used to search for a user based on their login, full name or email address.
|
||||
"""
|
||||
, Style.box_input "regex" "Regex" "regex" (up SEARCHUSER_INP_regex) searchUserForm.regex
|
||||
--, Style.btn_labeled "adminbtn" "Admin" (show searchUserForm.admin)
|
||||
, Web.box_input "regex" "Regex" "regex" (up SEARCHUSER_INP_regex) searchUserForm.regex
|
||||
--, Web.btn_labeled "adminbtn" "Admin" (show searchUserForm.admin)
|
||||
-- (HandleAddUserInput SEARCHUSER_toggle_admin)
|
||||
--, Style.box_input "domain" "Domain owned" "blah.netlib.re."
|
||||
--, Web.box_input "domain" "Domain owned" "blah.netlib.re."
|
||||
-- (up SEARCHUSER_INP_domain) searchUserForm.domain
|
||||
, Style.btn "Send" SearchUserAttempt
|
||||
, Web.btn "Send" SearchUserAttempt
|
||||
]
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
|
|
|
@ -17,7 +17,7 @@ import Halogen.HTML.Events as HE
|
|||
import Web.Event.Event as Event
|
||||
import Web.Event.Event (Event)
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
import Web.HTML (window) as HTML
|
||||
import Web.HTML.Window (sessionStorage) as Window
|
||||
|
@ -135,24 +135,24 @@ component =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { current_tab, authenticationForm, passwordRecoveryForm, newPasswordForm, errors } =
|
||||
Style.section_small
|
||||
Web.section_small
|
||||
[ fancy_tab_bar
|
||||
, if A.length errors > 0
|
||||
-- then HH.div_ [ Style.box [ HH.text (A.fold $ map show_error errors) ] ]
|
||||
then HH.div_ [ Style.box [Style.notification_danger (A.fold $ map show_error errors) CloseErrorStuff]]
|
||||
-- then HH.div_ [ Web.box [ HH.text (A.fold $ map show_error errors) ] ]
|
||||
then HH.div_ [ Web.box [Web.notification_danger (A.fold $ map show_error errors) CloseErrorStuff]]
|
||||
else HH.div_ []
|
||||
, case current_tab of
|
||||
Auth -> Style.box auth_form
|
||||
ILostMyPassword -> Style.box passrecovery_form
|
||||
Recovery -> Style.box newpass_form
|
||||
Auth -> Web.box auth_form
|
||||
ILostMyPassword -> Web.box passrecovery_form
|
||||
Recovery -> Web.box newpass_form
|
||||
]
|
||||
|
||||
where
|
||||
fancy_tab_bar =
|
||||
Style.fancy_tabs
|
||||
[ Style.tab_entry (is_tab_active Auth) "Authentication" (ChangeTab Auth)
|
||||
, Style.tab_entry (is_tab_active ILostMyPassword) "I lost my password! 😟" (ChangeTab ILostMyPassword)
|
||||
, Style.tab_entry (is_tab_active Recovery) "Recover with a token" (ChangeTab Recovery)
|
||||
Web.fancy_tabs
|
||||
[ Web.tab_entry (is_tab_active Auth) "Authentication" (ChangeTab Auth)
|
||||
, Web.tab_entry (is_tab_active ILostMyPassword) "I lost my password! 😟" (ChangeTab ILostMyPassword)
|
||||
, Web.tab_entry (is_tab_active Recovery) "Recover with a token" (ChangeTab Recovery)
|
||||
]
|
||||
is_tab_active tab = current_tab == tab
|
||||
|
||||
|
@ -203,45 +203,45 @@ render { current_tab, authenticationForm, passwordRecoveryForm, newPasswordForm,
|
|||
<> show min <> " and " <> show max
|
||||
<> " (currently: " <> show n <> ")"
|
||||
|
||||
auth_form = [ Style.h3 "Authentication", render_auth_form ]
|
||||
auth_form = [ Web.h3 "Authentication", render_auth_form ]
|
||||
passrecovery_form =
|
||||
[ Style.h3 "You forgot your password (or your login)"
|
||||
, Style.div_content []
|
||||
[ Style.p "Enter either your login or email and you'll receive a recovery token."
|
||||
[ Web.h3 "You forgot your password (or your login)"
|
||||
, Web.div_content []
|
||||
[ Web.p "Enter either your login or email and you'll receive a recovery token."
|
||||
]
|
||||
, render_password_recovery_form
|
||||
]
|
||||
newpass_form =
|
||||
[ Style.h3 "You got the password recovery mail"
|
||||
, Style.div_content []
|
||||
[ Style.p "Nice! You get to choose your new password."
|
||||
[ Web.h3 "You got the password recovery mail"
|
||||
, Web.div_content []
|
||||
[ Web.p "Nice! You get to choose your new password."
|
||||
]
|
||||
, render_new_password_form
|
||||
]
|
||||
|
||||
render_auth_form = HH.form
|
||||
[ HE.onSubmit AuthenticationAttempt ]
|
||||
[ Style.username_input "Username" authenticationForm.login (HandleAuthenticationInput <<< AUTH_INP_login)
|
||||
, Style.password_input "Password" authenticationForm.pass (HandleAuthenticationInput <<< AUTH_INP_pass)
|
||||
, Style.btn_validation
|
||||
[ Web.username_input "Username" authenticationForm.login (HandleAuthenticationInput <<< AUTH_INP_login)
|
||||
, Web.password_input "Password" authenticationForm.pass (HandleAuthenticationInput <<< AUTH_INP_pass)
|
||||
, Web.btn_validation
|
||||
]
|
||||
|
||||
render_password_recovery_form = HH.form
|
||||
[ HE.onSubmit PasswordRecoveryAttempt ]
|
||||
[ Style.username_input "Username" passwordRecoveryForm.login (HandlePasswordRecovery <<< PASSR_INP_login)
|
||||
, Style.email_input "Email" passwordRecoveryForm.email (HandlePasswordRecovery <<< PASSR_INP_email)
|
||||
, Style.btn_validation
|
||||
[ Web.username_input "Username" passwordRecoveryForm.login (HandlePasswordRecovery <<< PASSR_INP_login)
|
||||
, Web.email_input "Email" passwordRecoveryForm.email (HandlePasswordRecovery <<< PASSR_INP_email)
|
||||
, Web.btn_validation
|
||||
]
|
||||
|
||||
render_new_password_form = HH.form
|
||||
[ HE.onSubmit NewPasswordAttempt ]
|
||||
[ Style.username_input "Username" newPasswordForm.login (HandleNewPassword <<< NEWPASS_INP_login)
|
||||
, Style.token_input "Token" newPasswordForm.token (HandleNewPassword <<< NEWPASS_INP_token)
|
||||
, Style.password_input_new "Password" newPasswordForm.password (HandleNewPassword <<< NEWPASS_INP_password)
|
||||
[ Web.username_input "Username" newPasswordForm.login (HandleNewPassword <<< NEWPASS_INP_login)
|
||||
, Web.token_input "Token" newPasswordForm.token (HandleNewPassword <<< NEWPASS_INP_token)
|
||||
, Web.password_input_new "Password" newPasswordForm.password (HandleNewPassword <<< NEWPASS_INP_password)
|
||||
|
||||
, Style.password_input_confirmation "Confirmation" newPasswordForm.confirmation
|
||||
, Web.password_input_confirmation "Confirmation" newPasswordForm.confirmation
|
||||
(HandleNewPassword <<< NEWPASS_INP_confirmation)
|
||||
, Style.btn_validation
|
||||
, Web.btn_validation
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import Halogen.HTML.Events as HE
|
|||
import Halogen.HTML.Events as HHE
|
||||
import Web.Event.Event as Event
|
||||
import Web.Event.Event (Event)
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import App.Templates.Table (owned_domains, shared_domains) as Table
|
||||
|
||||
import App.DisplayErrors (error_to_paragraph_label)
|
||||
|
@ -179,41 +179,41 @@ initialState _ =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { accepted_domains, my_domains, newDomainForm, askDomainTransferForm, deletion_modal }
|
||||
= Style.section_small
|
||||
= Web.section_small
|
||||
[ case deletion_modal of
|
||||
Nothing -> HH.div_ [ Style.columns_ domain_line
|
||||
, Style.hr
|
||||
, Style.columns_ new_domain_line
|
||||
, Style.hr
|
||||
, Style.columns_ explanations_line
|
||||
Nothing -> HH.div_ [ Web.columns_ domain_line
|
||||
, Web.hr
|
||||
, Web.columns_ new_domain_line
|
||||
, Web.hr
|
||||
, Web.columns_ explanations_line
|
||||
]
|
||||
Just domain -> Style.modal "Deleting a domain"
|
||||
Just domain -> Web.modal "Deleting a domain"
|
||||
[warning_message domain] [modal_delete_button domain, modal_cancel_button]
|
||||
]
|
||||
where
|
||||
c = Style.column_
|
||||
c = Web.column_
|
||||
|
||||
domain_line = [ c render_my_domains, c render_my_shared_domains ]
|
||||
new_domain_line = [ c render_new_domain, c render_gain_ownership ]
|
||||
explanations_line = [ c render_share_ownership_explanation, c render_transfer_ownership_explanation ]
|
||||
|
||||
render_my_domains =
|
||||
[ Style.h3 "My domains"
|
||||
, Style.simple_quote "You are the exclusive owner of the following domains."
|
||||
[ Web.h3 "My domains"
|
||||
, Web.simple_quote "You are the exclusive owner of the following domains."
|
||||
, Table.owned_domains domains_i_exclusively_own EnterDomain TransferDomain ShareDomain DeleteDomainModal
|
||||
]
|
||||
render_my_shared_domains =
|
||||
[ Style.h3 "Shared domains"
|
||||
, Style.simple_quote """
|
||||
[ Web.h3 "Shared domains"
|
||||
, Web.simple_quote """
|
||||
The following domains are shared with other users.
|
||||
In case you are the last owner, you can "unshare" it and gain exclusive ownership.
|
||||
"""
|
||||
, Table.shared_domains domains_i_share EnterDomain UnShareDomain DeleteDomainModal
|
||||
]
|
||||
render_new_domain =
|
||||
[ Style.h3 "New domain"
|
||||
, Style.quote [ Style.p "The heart of dnsmanager! 🎉"
|
||||
, Style.p "You can reserve a domain name, right here."
|
||||
[ Web.h3 "New domain"
|
||||
, Web.quote [ Web.p "The heart of dnsmanager! 🎉"
|
||||
, Web.p "You can reserve a domain name, right here."
|
||||
, HH.text """
|
||||
Later you will be able to change the content, share, transfer or even delete the domain.
|
||||
"""
|
||||
|
@ -222,16 +222,16 @@ render { accepted_domains, my_domains, newDomainForm, askDomainTransferForm, del
|
|||
]
|
||||
|
||||
render_gain_ownership =
|
||||
[ Style.h3 "Get the ownership of a domain"
|
||||
, Style.simple_quote """
|
||||
[ Web.h3 "Get the ownership of a domain"
|
||||
, Web.simple_quote """
|
||||
Someone wants to give you (or share with you) the ownership of a domain.
|
||||
Please enter the UUID of the transfer.
|
||||
"""
|
||||
, render_ask_domain_transfer_form
|
||||
]
|
||||
render_share_ownership_explanation =
|
||||
[ Style.h3 "Share the ownership of a domain"
|
||||
, Style.simple_quote """
|
||||
[ Web.h3 "Share the ownership of a domain"
|
||||
, Web.simple_quote """
|
||||
Ask for a "share token" for your domain and give it to other users.
|
||||
All the owners can make modifications to the domain.
|
||||
Don't let the administration of a domain be the burden of a single person!
|
||||
|
@ -239,14 +239,14 @@ render { accepted_domains, my_domains, newDomainForm, askDomainTransferForm, del
|
|||
]
|
||||
|
||||
render_transfer_ownership_explanation =
|
||||
[ Style.h3 "Transfer the ownership of a domain"
|
||||
, Style.simple_quote """
|
||||
[ Web.h3 "Transfer the ownership of a domain"
|
||||
, Web.simple_quote """
|
||||
Ask for a transfer token for your domain and give it to the new owner.
|
||||
"""
|
||||
]
|
||||
|
||||
modal_delete_button domain = Style.alert_btn "Delete the domain" (RemoveDomain domain)
|
||||
modal_cancel_button = Style.cancel_button CancelModal
|
||||
modal_delete_button domain = Web.alert_btn "Delete the domain" (RemoveDomain domain)
|
||||
modal_cancel_button = Web.cancel_button CancelModal
|
||||
|
||||
-- I own all domain without a "share key".
|
||||
domains_i_exclusively_own = A.sort $ A.filter (\domain -> domain.share_key == Nothing) my_domains
|
||||
|
@ -259,19 +259,19 @@ render { accepted_domains, my_domains, newDomainForm, askDomainTransferForm, del
|
|||
<> "\". Are you sure you want to do this? This is "
|
||||
, HH.strong_ [ HH.text "irreversible" ]
|
||||
, HH.text "."
|
||||
, Style.notification_warning' """
|
||||
, Web.notification_warning' """
|
||||
In case this domain is shared, it will just be removed from your domains.
|
||||
"""
|
||||
]
|
||||
|
||||
render_add_domain_form = HH.form
|
||||
[ HE.onSubmit NewDomainAttempt ]
|
||||
[ Style.new_domain_field
|
||||
[ Web.new_domain_field
|
||||
(HandleNewDomainInput <<< INP_newdomain)
|
||||
newDomainForm.new_domain
|
||||
[ HHE.onSelectedIndexChange domain_choice ]
|
||||
(map (\v -> "." <> v) accepted_domains)
|
||||
, Style.btn_validation_ "add a new domain"
|
||||
, Web.btn_validation_ "add a new domain"
|
||||
, if A.length newDomainForm._errors > 0
|
||||
then HH.div_ $ map error_to_paragraph_label newDomainForm._errors
|
||||
else HH.div_ [ ]
|
||||
|
@ -279,10 +279,10 @@ render { accepted_domains, my_domains, newDomainForm, askDomainTransferForm, del
|
|||
|
||||
render_ask_domain_transfer_form = HH.form
|
||||
[ HE.onSubmit AskDomainTransferAttempt ]
|
||||
[ Style.box_input "idTransferToken" "Token" "UUID of the domain"
|
||||
[ Web.box_input "idTransferToken" "Token" "UUID of the domain"
|
||||
AskDomainTransferUUIDInput
|
||||
askDomainTransferForm.uuid
|
||||
, Style.btn_validation_ "ask for a domain transfer"
|
||||
, Web.btn_validation_ "ask for a domain transfer"
|
||||
, if A.length askDomainTransferForm._errors > 0
|
||||
then HH.div_ $ map error_to_paragraph_label askDomainTransferForm._errors
|
||||
else HH.div_ [ ]
|
||||
|
|
|
@ -13,7 +13,7 @@ import Halogen.HTML as HH
|
|||
import Halogen.HTML.Properties as HP
|
||||
|
||||
import CSSClasses as C
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
type Input = Unit
|
||||
type Action = Unit
|
||||
|
@ -41,38 +41,38 @@ initialState _ = unit
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render _ = HH.div_
|
||||
[ Style.hero_danger
|
||||
[ Web.hero_danger
|
||||
-- "THIS IS A BETA RELEASE"
|
||||
-- "You can register, login and play a bit with the tool. Feel free to report errors and suggestions."
|
||||
[ HH.text "MESSAGE DE MIGRATION" ]
|
||||
[ Style.p "En bref : le site a été refait à neuf, en anglais (une traduction arrivera… un jour), et les comptes ont été migrés."
|
||||
, Style.p "Les utilisateurs peuvent se connecter avec leurs identifiants habituels et leurs domaines sont toujours attribués, mais le contenu n'a pas été préservé (il faut re-configurer les zones)."
|
||||
, Style.p """
|
||||
[ Web.p "En bref : le site a été refait à neuf, en anglais (une traduction arrivera… un jour), et les comptes ont été migrés."
|
||||
, Web.p "Les utilisateurs peuvent se connecter avec leurs identifiants habituels et leurs domaines sont toujours attribués, mais le contenu n'a pas été préservé (il faut re-configurer les zones)."
|
||||
, Web.p """
|
||||
Les comptes migrés sont conservés 6 mois, puis supprimés si aucune connexion n'est faite,
|
||||
afin de purger un certain nombre de vieux comptes de robots.
|
||||
"""
|
||||
, HH.p [ HP.classes [C.margin_top 3] ]
|
||||
[ Style.outside_link [C.button, C.is_info] url_linuxfr "Article linuxfr.org présentant la nouvelle version de netlibre" ]
|
||||
[ Web.outside_link [C.button, C.is_info] url_linuxfr "Article linuxfr.org présentant la nouvelle version de netlibre" ]
|
||||
]
|
||||
, Style.section_small
|
||||
[ Style.h1 "Welcome to netlib.re"
|
||||
, Style.subtitle "Free domain names for the common folks"
|
||||
, Style.hr
|
||||
, Web.section_small
|
||||
[ Web.h1 "Welcome to netlib.re"
|
||||
, Web.subtitle "Free domain names for the common folks"
|
||||
, Web.hr
|
||||
, render_description
|
||||
, render_update_why_and_contact
|
||||
, Style.hr
|
||||
, Web.hr
|
||||
, render_how_and_code
|
||||
]
|
||||
]
|
||||
where
|
||||
url_linuxfr = "https://linuxfr.org/news/netlibre-un-service-libre-et-un-nom-de-domaine-gratuit"
|
||||
title = Style.h3
|
||||
expl content = Style.div_content [] [ Style.explanation content ]
|
||||
p = Style.p
|
||||
b x = Style.column_ [ Style.box [ Style.div_content [] x ] ]
|
||||
title = Web.h3
|
||||
expl content = Web.div_content [] [ Web.explanation content ]
|
||||
p = Web.p
|
||||
b x = Web.column_ [ Web.box [ Web.div_content [] x ] ]
|
||||
|
||||
render_description = Style.columns_ [ render_basics, render_no_expert, render_no_housing ]
|
||||
render_update_why_and_contact = Style.columns_ [ render_updates, render_why, render_contact ]
|
||||
render_description = Web.columns_ [ render_basics, render_no_expert, render_no_housing ]
|
||||
render_update_why_and_contact = Web.columns_ [ render_updates, render_why, render_contact ]
|
||||
|
||||
render_basics
|
||||
= b [ title "What is provided?"
|
||||
|
@ -96,7 +96,7 @@ render _ = HH.div_
|
|||
render_updates
|
||||
= b [ title "Automatic updates"
|
||||
, p "Update your records with a single, stupidly simple command. For example:"
|
||||
, expl [ Style.strong "wget https://www.netlib.re/token-update/<token>" ]
|
||||
, expl [ Web.strong "wget https://www.netlib.re/token-update/<token>" ]
|
||||
, p "Every A and AAAA records have tokens for easy updates."
|
||||
]
|
||||
|
||||
|
@ -112,7 +112,7 @@ render _ = HH.div_
|
|||
, p "For legal matter: abuse@netlib.re"
|
||||
]
|
||||
|
||||
render_how_and_code = Style.columns_ [ render_how, render_code ]
|
||||
render_how_and_code = Web.columns_ [ render_how, render_code ]
|
||||
render_how
|
||||
= b [ title "How does this work?"
|
||||
, p "We pay for the domain names (netlib.re and codelib.re) and let you have a subdomain."
|
||||
|
@ -137,8 +137,8 @@ render _ = HH.div_
|
|||
this user-friendly website, so you can manage your zones. 🥰
|
||||
"""
|
||||
]
|
||||
, Style.hr
|
||||
, Style.p "But of course, there are a few more technical parts:"
|
||||
, Web.hr
|
||||
, Web.p "But of course, there are a few more technical parts:"
|
||||
, HH.ul_
|
||||
[ link "https://git.baguette.netlib.re/Baguette/libipc" "libIPC"
|
||||
"""
|
||||
|
@ -154,4 +154,4 @@ render _ = HH.div_
|
|||
]
|
||||
]
|
||||
link url link_title content
|
||||
= HH.li_ [ Style.outside_link [] url link_title, HH.text ", ", HH.text content ]
|
||||
= HH.li_ [ Web.outside_link [] url link_title, HH.text ", ", HH.text content ]
|
||||
|
|
|
@ -16,7 +16,7 @@ import Halogen.HTML.Events as HE
|
|||
import Web.Event.Event as Event
|
||||
import Web.Event.Event (Event)
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
import App.Type.LogMessage
|
||||
import App.Message.AuthenticationDaemon as AuthD
|
||||
|
@ -82,21 +82,21 @@ initialState _ =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { mailValidationForm }
|
||||
= Style.section_small [ Style.columns_ [ b mail_validation_form ] ]
|
||||
= Web.section_small [ Web.columns_ [ b mail_validation_form ] ]
|
||||
|
||||
where
|
||||
b e = Style.column_ [ Style.box e ]
|
||||
b e = Web.column_ [ Web.box e ]
|
||||
mail_validation_form
|
||||
= [ Style.h3 "Verify your account"
|
||||
, Style.div_content [] [Style.explanation [Style.p "Email addresses must be validated within 30 minutes."]]
|
||||
= [ Web.h3 "Verify your account"
|
||||
, Web.div_content [] [Web.explanation [Web.p "Email addresses must be validated within 30 minutes."]]
|
||||
, render_register_form
|
||||
]
|
||||
|
||||
render_register_form = HH.form
|
||||
[ HE.onSubmit ValidateInputs ]
|
||||
[ Style.username_input "Username" mailValidationForm.login (HandleValidationInput <<< VALIDATION_INP_login)
|
||||
, Style.token_input "Token" mailValidationForm.token (HandleValidationInput <<< VALIDATION_INP_token)
|
||||
, Style.btn_validation
|
||||
[ Web.username_input "Username" mailValidationForm.login (HandleValidationInput <<< VALIDATION_INP_login)
|
||||
, Web.token_input "Token" mailValidationForm.token (HandleValidationInput <<< VALIDATION_INP_token)
|
||||
, Web.btn_validation
|
||||
]
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
|
|
|
@ -36,7 +36,7 @@ import Web.Event.Event (Event)
|
|||
-- import Data.Generic.Rep (class Generic)
|
||||
-- import Data.Show.Generic (genericShow)
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import Scroll (scrollToTop)
|
||||
|
||||
import App.Type.LogMessage
|
||||
|
@ -113,39 +113,39 @@ component =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render state
|
||||
= Style.section_small [Style.columns_
|
||||
= Web.section_small [Web.columns_
|
||||
[ b email_form
|
||||
, b token_form
|
||||
]]
|
||||
|
||||
where
|
||||
b e = Style.column_ [ Style.box e ]
|
||||
b e = Web.column_ [ Web.box e ]
|
||||
|
||||
email_form
|
||||
= [ Style.h3 "New Email address"
|
||||
= [ Web.h3 "New Email address"
|
||||
-- TODO: put some text here
|
||||
, HH.form
|
||||
[ HE.onSubmit (Verify EmailAddress) ]
|
||||
[ email_input, email_error, Style.btn_validation ]
|
||||
[ email_input, email_error, Web.btn_validation ]
|
||||
]
|
||||
|
||||
email_input = Style.email_input "Email" state.email (UserInput EmailAddress)
|
||||
email_input = Web.email_input "Email" state.email (UserInput EmailAddress)
|
||||
|
||||
email_error
|
||||
= case between 0 5 (S.length state.email), E.email state.email of
|
||||
true, _ -> HH.text ""
|
||||
_, Left errors -> Style.error_box "newAddress" "Email error" (show_error $ Email errors)
|
||||
_, Left errors -> Web.error_box "newAddress" "Email error" (show_error $ Email errors)
|
||||
_, Right _ -> HH.text ""
|
||||
|
||||
token_form
|
||||
= [ Style.h3 "Email validation token"
|
||||
= [ Web.h3 "Email validation token"
|
||||
-- TODO: put some text here
|
||||
, HH.form
|
||||
[ HE.onSubmit (Verify Token) ]
|
||||
[ token_input {-, token_error -}, Style.btn_validation ]
|
||||
[ token_input {-, token_error -}, Web.btn_validation ]
|
||||
]
|
||||
|
||||
token_input = Style.token_input "Token" state.token (UserInput Token)
|
||||
token_input = Web.token_input "Token" state.token (UserInput Token)
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
handleAction = case _ of
|
||||
|
|
|
@ -18,7 +18,7 @@ import Halogen.HTML.Properties as HP
|
|||
import Halogen.HTML.Properties.ARIA as ARIA
|
||||
|
||||
import CSSClasses as C
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
|
||||
import App.Type.Pages (Page(..))
|
||||
import App.Type.LogMessage (LogMessage)
|
||||
|
@ -142,7 +142,7 @@ render { logged, active, admin, login } =
|
|||
HH.a [ HP.classes $ [C.navbar_burger] <> if active then [C.is_active] else []
|
||||
, ARIA.label "menu"
|
||||
, ARIA.expanded "false"
|
||||
, Style.data_target "navbar-netlibre"
|
||||
, Web.data_target "navbar-netlibre"
|
||||
, HE.onClick (\_ -> ToggleMenu)
|
||||
] [ HH.span [ARIA.hidden "true"] []
|
||||
, HH.span [ARIA.hidden "true"] []
|
||||
|
@ -192,7 +192,7 @@ render { logged, active, admin, login } =
|
|||
, HE.onClick (\_ -> action)
|
||||
] [ (HH.text str) ]
|
||||
|
||||
dropdown_element classes link str = Style.outside_link ([C.navbar_item] <> classes) link str
|
||||
dropdown_element classes link str = Web.outside_link ([C.navbar_item] <> classes) link str
|
||||
dropdown_element_primary link str = dropdown_element [C.has_background_info_light] link str
|
||||
dropdown_element_secondary link str = dropdown_element [C.has_background_warning_light] link str
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import Web.Event.Event (Event)
|
|||
|
||||
import App.Text.Explanations as Explanations
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import CSSClasses as C
|
||||
|
||||
import Data.String as S
|
||||
|
@ -96,11 +96,11 @@ component =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { registrationForm }
|
||||
= Style.section_small [Style.columns_ [ b registration_form ]]
|
||||
= Web.section_small [Web.columns_ [ b registration_form ]]
|
||||
|
||||
where
|
||||
b e = Style.column_ [ Style.box e ]
|
||||
registration_form = [ Style.h3 "Register", render_register_form ]
|
||||
b e = Web.column_ [ Web.box e ]
|
||||
registration_form = [ Web.h3 "Register", render_register_form ]
|
||||
|
||||
render_register_form = HH.form
|
||||
[ HE.onSubmit ValidateInputs ]
|
||||
|
@ -109,39 +109,39 @@ render { registrationForm }
|
|||
password_input <> password_error <>
|
||||
legal_mentions <> validation_btn)
|
||||
|
||||
username_input = [ Style.username_input "Username" registrationForm.login (HandleRegisterInput <<< REG_INP_login) ]
|
||||
username_input = [ Web.username_input "Username" registrationForm.login (HandleRegisterInput <<< REG_INP_login) ]
|
||||
|
||||
username_error
|
||||
= case between 0 1 (S.length registrationForm.login), L.login registrationForm.login of
|
||||
true, _ -> []
|
||||
_, Left errors -> [ Style.error_box "loginREGISTER" "Login error" (show_error $ Login errors) ]
|
||||
_, Left errors -> [ Web.error_box "loginREGISTER" "Login error" (show_error $ Login errors) ]
|
||||
_, Right _ -> []
|
||||
|
||||
email_input = [ Style.email_input "Email" registrationForm.email (HandleRegisterInput <<< REG_INP_email) ]
|
||||
email_input = [ Web.email_input "Email" registrationForm.email (HandleRegisterInput <<< REG_INP_email) ]
|
||||
|
||||
email_error
|
||||
= case between 0 5 (S.length registrationForm.email), E.email registrationForm.email of
|
||||
true, _ -> []
|
||||
_, Left errors -> [ Style.error_box "emailREGISTER" "Email error" (show_error $ Email errors) ]
|
||||
_, Left errors -> [ Web.error_box "emailREGISTER" "Email error" (show_error $ Email errors) ]
|
||||
_, Right _ -> []
|
||||
|
||||
password_input = [ Style.password_input "Password" registrationForm.pass (HandleRegisterInput <<< REG_INP_pass) ]
|
||||
password_input = [ Web.password_input "Password" registrationForm.pass (HandleRegisterInput <<< REG_INP_pass) ]
|
||||
|
||||
password_error
|
||||
= case between 0 15 (S.length registrationForm.pass), P.password registrationForm.pass of
|
||||
true, _ -> []
|
||||
_, Left errors -> [ Style.error_box "passwordREGISTER" "Password error" (show_error $ Password errors) ]
|
||||
_, Left errors -> [ Web.error_box "passwordREGISTER" "Password error" (show_error $ Password errors) ]
|
||||
_, Right _ -> []
|
||||
|
||||
legal_mentions = [ Explanations.legal_notice
|
||||
, HH.div [HP.classes [C.margin_top 3, C.margin_bottom 3]]
|
||||
[ Style.checkbox
|
||||
[ Web.checkbox
|
||||
[HH.span [HP.classes [C.margin_left 3]] [HH.text "I have read and accept the terms of service and privacy policy."]]
|
||||
LegalCheckboxToggle
|
||||
]
|
||||
]
|
||||
|
||||
validation_btn = [ Style.btn_validation ]
|
||||
validation_btn = [ Web.btn_validation ]
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
handleAction = case _ of
|
||||
|
|
|
@ -15,7 +15,7 @@ import Halogen.HTML.Events as HE
|
|||
import Web.Event.Event as Event
|
||||
import Web.Event.Event (Event)
|
||||
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import CSSClasses as C
|
||||
|
||||
import App.Type.Email as Email
|
||||
|
@ -89,50 +89,50 @@ initialState emails =
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render { modal, newPasswordForm, emails } =
|
||||
Style.section_small
|
||||
Web.section_small
|
||||
[ render_emails emails
|
||||
, Style.hr
|
||||
, Web.hr
|
||||
, case modal of
|
||||
DeleteAccountModal -> render_delete_account_modal
|
||||
NoModal -> Style.columns_
|
||||
[ b [ Style.btn_ [C.is_large, C.is_info] "Change email address" RouteChangeEmailAddressPage ]
|
||||
, b [ Style.h3 "Change password", render_new_password_form ]
|
||||
, b [ Style.h3 "Delete account", render_delete_account ]
|
||||
NoModal -> Web.columns_
|
||||
[ b [ Web.btn_ [C.is_large, C.is_info] "Change email address" RouteChangeEmailAddressPage ]
|
||||
, b [ Web.h3 "Change password", render_new_password_form ]
|
||||
, b [ Web.h3 "Delete account", render_delete_account ]
|
||||
]
|
||||
]
|
||||
|
||||
where
|
||||
b e = Style.column_ e
|
||||
b e = Web.column_ e
|
||||
|
||||
render_emails (Tuple current pending) = HH.div [] $ render_current current <> render_pending pending
|
||||
where
|
||||
render_current (Just (Email.Email e)) = [ Style.p $ "Current email address: " ] <>
|
||||
[ Style.btn_ro [C.is_small, C.is_warning] e]
|
||||
render_current Nothing = [ Style.p "You do not currently have a validated email address." ]
|
||||
render_current (Just (Email.Email e)) = [ Web.p $ "Current email address: " ] <>
|
||||
[ Web.btn_ro [C.is_small, C.is_warning] e]
|
||||
render_current Nothing = [ Web.p "You do not currently have a validated email address." ]
|
||||
|
||||
render_pending (Just (Email.Email e)) = [ Style.p $ "Pending email address: " ] <>
|
||||
[ Style.btn_ro [C.is_small, C.is_warning] e]
|
||||
render_pending (Just (Email.Email e)) = [ Web.p $ "Pending email address: " ] <>
|
||||
[ Web.btn_ro [C.is_small, C.is_warning] e]
|
||||
render_pending Nothing = []
|
||||
|
||||
render_delete_account = Style.alert_btn "Delete my account" DeleteAccountPopup
|
||||
render_delete_account = Web.alert_btn "Delete my account" DeleteAccountPopup
|
||||
|
||||
render_new_password_form = HH.form
|
||||
[ HE.onSubmit ChangePasswordAttempt ]
|
||||
[ Style.box_password "passwordNEWPASS" "New Password" "password"
|
||||
[ Web.box_password "passwordNEWPASS" "New Password" "password"
|
||||
(HandleNewPassword <<< NEWPASS_INP_password)
|
||||
newPasswordForm.password
|
||||
, Style.box_password "confirmationNEWPASS" "Confirmation" "confirmation"
|
||||
, Web.box_password "confirmationNEWPASS" "Confirmation" "confirmation"
|
||||
(HandleNewPassword <<< NEWPASS_INP_confirmation)
|
||||
newPasswordForm.confirmation
|
||||
, Style.btn_validation
|
||||
, Web.btn_validation
|
||||
]
|
||||
|
||||
render_delete_account_modal = Style.modal "Delete your account"
|
||||
[ Style.p "Your account and domains will be removed."
|
||||
, Style.strong "⚠ You won't be able to recover your data."
|
||||
render_delete_account_modal = Web.modal "Delete your account"
|
||||
[ Web.p "Your account and domains will be removed."
|
||||
, Web.strong "⚠ You won't be able to recover your data."
|
||||
]
|
||||
[ Style.alert_btn "GO AHEAD LOL" DeleteAccount
|
||||
, Style.cancel_button CancelModal
|
||||
[ Web.alert_btn "GO AHEAD LOL" DeleteAccount
|
||||
, Web.cancel_button CancelModal
|
||||
]
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
|
|
|
@ -17,6 +17,7 @@ import Prelude (class Show, Unit, bind, comparing, discard, map, pure, show, uni
|
|||
import Data.Generic.Rep (class Generic)
|
||||
import Data.Show.Generic (genericShow)
|
||||
|
||||
import App.Templates.Modal as Modal
|
||||
import Web.HTML (window) as HTML
|
||||
import Web.HTML.Window (sessionStorage) as Window
|
||||
import Web.Storage.Storage as Storage
|
||||
|
@ -42,11 +43,14 @@ import Halogen.HTML as HH
|
|||
import Halogen.HTML.Properties as HP
|
||||
|
||||
import App.Templates.Table as Table
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import CSSClasses as C
|
||||
|
||||
import App.Text.Explanations as Explanations
|
||||
|
||||
import App.Type.RRId
|
||||
import App.Type.Field as Field
|
||||
import App.Type.RRModal
|
||||
import App.Type.AcceptedRRTypes (AcceptedRRTypes(..))
|
||||
import App.Type.ResourceRecord (ResourceRecord
|
||||
, emptyRR, mechanism_types, modifier_types, qualifier_types
|
||||
|
@ -56,14 +60,10 @@ import App.Type.ResourceRecord (Mechanism, Modifier, Qualifier(..), SRVProtocol(
|
|||
import App.Type.DKIM as DKIM
|
||||
import App.Type.DMARC as DMARC
|
||||
|
||||
import App.DisplayErrors (error_to_paragraph, show_error_email)
|
||||
|
||||
import App.Type.LogMessage (LogMessage(..))
|
||||
import App.Message.DNSManagerDaemon as DNSManager
|
||||
import App.Validation.DNS as Validation
|
||||
|
||||
type RRId = Int
|
||||
|
||||
-- | `App.Page.Zone` can send messages through websocket interface
|
||||
-- | connected to dnsmanagerd. See `App.WS`.
|
||||
-- |
|
||||
|
@ -86,21 +86,6 @@ type Slot = H.Slot Query Output
|
|||
|
||||
type Input = String
|
||||
|
||||
data Field
|
||||
= Field_Domain String
|
||||
| Field_TTL String
|
||||
| Field_Target String
|
||||
| Field_Priority String
|
||||
| Field_Weight String
|
||||
| Field_Port String
|
||||
| Field_SPF_v String
|
||||
| Field_SPF_mechanisms (Array RR.Mechanism)
|
||||
| Field_SPF_modifiers (Array RR.Modifier)
|
||||
| Field_SPF_q RR.Qualifier
|
||||
|
||||
| Field_CAA_flag String
|
||||
| Field_CAA_value String
|
||||
|
||||
-- | Steps to create a new RR:
|
||||
-- | 1. `CreateNewRRModal AcceptedRRTypes`: create a modal with default values based on selected accepted type.
|
||||
-- | 2. `UpdateCurrentRR Field`: modify the fields of the future new RR.
|
||||
|
@ -137,7 +122,7 @@ data Action
|
|||
| ReturnToDomainList
|
||||
|
||||
-- | Update new entry form (in the `rr_modal` modal).
|
||||
| UpdateCurrentRR Field
|
||||
| UpdateCurrentRR Field.Field
|
||||
|
||||
-- | Validate a new resource record before adding it.
|
||||
| ValidateRR AcceptedRRTypes
|
||||
|
@ -159,71 +144,11 @@ data Action
|
|||
-- | Automatically closes the modal.
|
||||
| RemoveRR RRId
|
||||
|
||||
-- | Ask a (new) token for a RR.
|
||||
| NewToken RRId
|
||||
|
||||
-- | Ask `dnsmanagerd` for the generated zone file.
|
||||
| AskZoneFile
|
||||
|
||||
| CAA_tag Int
|
||||
|
||||
| SRV_Protocol Int
|
||||
|
||||
| SPF_Mechanism_q Int
|
||||
| SPF_Mechanism_t Int
|
||||
| SPF_Mechanism_v String
|
||||
| SPF_Modifier_t Int
|
||||
| SPF_Modifier_v String
|
||||
| SPF_Qualifier Int
|
||||
|
||||
-- | Remove a SPF mechanism of the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_remove_mechanism Int
|
||||
-- | Remove a SPF modifier of the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_remove_modifier Int
|
||||
|
||||
-- | Add a SPF mechanism to the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_Mechanism_Add
|
||||
-- | Add a SPF modifier to the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_Modifier_Add
|
||||
|
||||
-- | Change the temporary mail address for DMARC.
|
||||
| DMARC_mail String
|
||||
|
||||
-- | Change the temporary report size limit for DMARC.
|
||||
| DMARC_mail_limit String
|
||||
|
||||
-- | Change the requested report interval.
|
||||
| DMARC_ri String
|
||||
|
||||
-- | Add a new mail address to the DMARC rua list.
|
||||
| DMARC_rua_Add
|
||||
|
||||
-- | Add a new mail address to the DMARC ruf list.
|
||||
| DMARC_ruf_Add
|
||||
|
||||
-- | Remove a mail address of the DMARC rua list.
|
||||
| DMARC_remove_rua Int
|
||||
|
||||
-- | Remove a mail address of the DMARC ruf list.
|
||||
| DMARC_remove_ruf Int
|
||||
|
||||
| DMARC_policy Int
|
||||
| DMARC_sp_policy Int
|
||||
| DMARC_adkim Int
|
||||
| DMARC_aspf Int
|
||||
| DMARC_pct String
|
||||
| DMARC_fo Int
|
||||
|
||||
| DKIM_hash_algo Int
|
||||
| DKIM_sign_algo Int
|
||||
| DKIM_pubkey String
|
||||
| DKIM_note String
|
||||
|
||||
data RRModal
|
||||
= NoModal
|
||||
| NewRRModal AcceptedRRTypes
|
||||
| UpdateRRModal
|
||||
| RemoveRRModal RRId
|
||||
-- | Modification of any attribute of the current RR.
|
||||
| RRUpdate RRUpdateValue
|
||||
|
||||
data Tab = Zone | TheBasics | TokenExplanation
|
||||
derive instance eqTab :: Eq Tab
|
||||
|
@ -231,6 +156,9 @@ derive instance genericTab :: Generic Tab _
|
|||
instance showTab :: Show Tab where
|
||||
show = genericShow
|
||||
|
||||
import App.Type.RRForm
|
||||
|
||||
-- FIXME: this state is a mess.
|
||||
type State =
|
||||
{ _domain :: String
|
||||
|
||||
|
@ -242,24 +170,7 @@ type State =
|
|||
--, _local_errors :: Hash.HashMap RRId (Array Validation.Error)
|
||||
|
||||
-- Unique RR form.
|
||||
, _currentRR :: ResourceRecord
|
||||
, _currentRR_errors :: Array Validation.Error
|
||||
, _dmarc_mail_errors :: Array Email.Error
|
||||
|
||||
-- SPF details.
|
||||
, spf_mechanism_q :: String
|
||||
, spf_mechanism_t :: String
|
||||
, spf_mechanism_v :: String
|
||||
, spf_modifier_t :: String
|
||||
, spf_modifier_v :: String
|
||||
|
||||
, dmarc_mail :: String
|
||||
, dmarc_mail_limit :: Maybe Int
|
||||
|
||||
, dkim :: DKIM.DKIM
|
||||
, dmarc :: DMARC.DMARC
|
||||
|
||||
, _zonefile :: Maybe String
|
||||
, _rr_form :: RRForm
|
||||
|
||||
, current_tab :: Tab
|
||||
}
|
||||
|
@ -281,15 +192,6 @@ component =
|
|||
default_domain :: String
|
||||
default_domain = "netlib.re"
|
||||
|
||||
default_rr_A :: ResourceRecord
|
||||
default_rr_A = emptyRR { rrtype = "A", name = "server1", target = "192.0.2.1" }
|
||||
|
||||
default_empty_rr :: ResourceRecord
|
||||
default_empty_rr = default_rr_A
|
||||
|
||||
default_qualifier_str = "hard_fail" :: String
|
||||
default_caa = { flag: 0, tag: CAA.Issue, value: "letsencrypt.org" } :: CAA.CAA
|
||||
|
||||
initialState :: Input -> State
|
||||
initialState domain =
|
||||
{ rr_modal: NoModal
|
||||
|
@ -299,24 +201,7 @@ initialState domain =
|
|||
, _resources: []
|
||||
--, _local_errors: Hash.empty
|
||||
|
||||
-- This is the state for the new RR modal.
|
||||
, _currentRR: default_empty_rr
|
||||
-- List of errors within the form in new RR modal.
|
||||
, _currentRR_errors: []
|
||||
, _dmarc_mail_errors: []
|
||||
, _zonefile: Nothing
|
||||
|
||||
, spf_mechanism_q: "pass"
|
||||
, spf_mechanism_t: "a"
|
||||
, spf_mechanism_v: ""
|
||||
, spf_modifier_t: "redirect"
|
||||
, spf_modifier_v: ""
|
||||
|
||||
, dkim: DKIM.emptyDKIMRR
|
||||
, dmarc: DMARC.emptyDMARCRR
|
||||
|
||||
, dmarc_mail: ""
|
||||
, dmarc_mail_limit: Nothing
|
||||
, _rr_form: mkEmptyRRForm
|
||||
|
||||
, current_tab: Zone
|
||||
}
|
||||
|
@ -325,7 +210,7 @@ type SortableRecord l = Record (rrtype :: String, rrid :: Int | l)
|
|||
|
||||
render :: forall m. State -> H.ComponentHTML Action () m
|
||||
render state
|
||||
= Style.section_small
|
||||
= Web.section_small
|
||||
[ fancy_tab
|
||||
, case state.current_tab of
|
||||
Zone -> render_zone
|
||||
|
@ -334,25 +219,29 @@ render state
|
|||
]
|
||||
where
|
||||
fancy_tab =
|
||||
Style.fancy_tabs
|
||||
[ Style.tab_entry (is_tab_active Zone) "Zone" (ChangeTab Zone)
|
||||
, Style.tab_entry (is_tab_active TheBasics) "The basics 🧠" (ChangeTab TheBasics)
|
||||
, Style.tab_entry (is_tab_active TokenExplanation) "Tokens? 🤨" (ChangeTab TokenExplanation)
|
||||
Web.fancy_tabs
|
||||
[ Web.tab_entry (is_tab_active Zone) "Zone" (ChangeTab Zone)
|
||||
, Web.tab_entry (is_tab_active TheBasics) "The basics 🧠" (ChangeTab TheBasics)
|
||||
, Web.tab_entry (is_tab_active TokenExplanation) "Tokens? 🤨" (ChangeTab TokenExplanation)
|
||||
]
|
||||
is_tab_active tab = state.current_tab == tab
|
||||
|
||||
call_to_current_rr_modal
|
||||
= Modal.current_rr_modal state._domain state._currentRR state.rr_modal
|
||||
UpdateCurrentRR NewToken RRUpdate ValidateRR ValidateLocal CancelModal
|
||||
|
||||
render_zone =
|
||||
case state.rr_modal of
|
||||
RemoveRRModal rr_id -> modal_rr_delete rr_id
|
||||
NewRRModal _ -> render_current_rr_modal
|
||||
UpdateRRModal -> render_current_rr_modal
|
||||
RemoveRRModal rr_id -> Modal.modal_rr_delete rr_id RemoveRR CancelModal
|
||||
NewRRModal _ -> call_to_current_rr_modal
|
||||
UpdateRRModal -> call_to_current_rr_modal
|
||||
NoModal -> HH.div_
|
||||
[ Style.level [ Style.btn_ [C.is_large, C.is_info] "Back to the domain list" ReturnToDomainList
|
||||
, Style.h1 state._domain
|
||||
[ Web.level [ Web.btn_ [C.is_large, C.is_info] "Back to the domain list" ReturnToDomainList
|
||||
, Web.h1 state._domain
|
||||
] []
|
||||
, Style.hr
|
||||
, Web.hr
|
||||
, Table.resource_records (sorted state._resources) CreateUpdateRRModal DeleteRRModal NewToken
|
||||
, Style.hr
|
||||
, Web.hr
|
||||
, render_new_records state
|
||||
, render_zonefile state._zonefile
|
||||
]
|
||||
|
@ -364,319 +253,6 @@ render state
|
|||
# map (A.sortBy (comparing (_.rrid))) -- -> [[x1x2][y][z1z2z3]]
|
||||
# A.concat -- -> [x1 x2 y z1 z2 z3]
|
||||
|
||||
modal_rr_delete :: forall w. Int -> HH.HTML w Action
|
||||
modal_rr_delete rr_id = Style.modal "Deleting a resource record"
|
||||
[warning_message] [modal_delete_button, Style.cancel_button CancelModal]
|
||||
where
|
||||
modal_delete_button = Style.alert_btn "Delete the resource record" (RemoveRR rr_id)
|
||||
warning_message
|
||||
= HH.p [] [ HH.text "You are about to delete a resource record, this action is "
|
||||
, Style.strong "irreversible"
|
||||
, HH.text "."
|
||||
]
|
||||
|
||||
render_current_rr_modal :: forall w. HH.HTML w Action
|
||||
render_current_rr_modal =
|
||||
case state._currentRR.rrtype of
|
||||
"A" -> template (modal_content_simple A) (foot_content A)
|
||||
"AAAA" -> template (modal_content_simple AAAA) (foot_content AAAA)
|
||||
"TXT" -> template (modal_content_simple TXT) (foot_content TXT)
|
||||
"CNAME" -> template (modal_content_simple CNAME) (foot_content CNAME)
|
||||
"NS" -> template (modal_content_simple NS) (foot_content NS)
|
||||
"MX" -> template modal_content_mx (foot_content MX)
|
||||
"CAA" -> template modal_content_caa (foot_content CAA)
|
||||
"SRV" -> template modal_content_srv (foot_content SRV)
|
||||
"SPF" -> template modal_content_spf (foot_content SPF)
|
||||
"DKIM" -> template modal_content_dkim (foot_content DKIM)
|
||||
"DMARC" -> template modal_content_dmarc (foot_content DMARC)
|
||||
_ -> Style.p $ "Invalid type: " <> state._currentRR.rrtype
|
||||
where
|
||||
side_text_for_name_input name_id = Style.side_text_above_input name_id "Name" (HH.text $ "Empty name = root domain (" <> state._domain <> ".)")
|
||||
-- DRY
|
||||
updateForm x = UpdateCurrentRR <<< x
|
||||
render_errors = if A.length state._currentRR_errors > 0
|
||||
then HH.div_ $ [ Style.h3 "Errors: " ] <> map error_to_paragraph state._currentRR_errors
|
||||
else HH.div_ [ ]
|
||||
modal_content_simple :: AcceptedRRTypes -> Array (HH.HTML w Action)
|
||||
modal_content_simple x =
|
||||
[ render_errors
|
||||
, render_introduction_text x
|
||||
, side_text_for_name_input ("domain" <> state._currentRR.rrtype)
|
||||
, Style.input_with_side_text ("domain" <> state._currentRR.rrtype) "" "www"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input ("ttl" <> state._currentRR.rrtype) "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
, case state._currentRR.rrtype of
|
||||
"AAAA" -> Style.box_input ("target" <> state._currentRR.rrtype) "Target" "2001:db8::1" (updateForm Field_Target) state._currentRR.target
|
||||
"TXT" -> Style.box_input ("target" <> state._currentRR.rrtype) "Your text" "blah blah" (updateForm Field_Target) state._currentRR.target
|
||||
"CNAME" -> Style.box_input ("target" <> state._currentRR.rrtype) "Target" "www" (updateForm Field_Target) state._currentRR.target
|
||||
"NS" -> Style.box_input ("target" <> state._currentRR.rrtype) "Target" "ns0.example.com." (updateForm Field_Target) state._currentRR.target
|
||||
_ -> Style.box_input ("target" <> state._currentRR.rrtype) "Target" "198.51.100.5" (updateForm Field_Target) state._currentRR.target
|
||||
] <> case state.rr_modal of
|
||||
UpdateRRModal ->
|
||||
if A.elem state._currentRR.rrtype ["A", "AAAA"]
|
||||
then [ Style.field_entry ("token" <> state._currentRR.rrtype) "Token"
|
||||
(maybe (Style.text "❌") Style.p state._currentRR.token)
|
||||
]
|
||||
else []
|
||||
_ -> []
|
||||
|
||||
render_introduction_text :: AcceptedRRTypes -> HH.HTML w Action
|
||||
render_introduction_text = case _ of
|
||||
A -> Style.div_content [] [Style.explanation Explanations.a_introduction]
|
||||
AAAA -> Style.div_content [] [Style.explanation Explanations.aaaa_introduction]
|
||||
TXT -> Style.div_content [] [Style.explanation Explanations.txt_introduction]
|
||||
CNAME -> Style.div_content [] [Style.explanation Explanations.cname_introduction]
|
||||
NS -> Style.div_content [] [Style.explanation Explanations.ns_introduction]
|
||||
_ -> HH.p_ []
|
||||
modal_content_mx :: Array (HH.HTML w Action)
|
||||
modal_content_mx =
|
||||
[ render_errors
|
||||
, Style.div_content [] [Style.explanation Explanations.mx_introduction]
|
||||
, side_text_for_name_input "domainMX"
|
||||
, Style.input_with_side_text "domainMX" "" "www"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input ("ttlMX") "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
, Style.box_input ("targetMX") "Target" "www"
|
||||
(updateForm Field_Target)
|
||||
state._currentRR.target
|
||||
, Style.box_input ("priorityMX") "Priority" "10"
|
||||
(updateForm Field_Priority)
|
||||
(maybe "" show state._currentRR.priority)
|
||||
]
|
||||
modal_content_caa :: Array (HH.HTML w Action)
|
||||
modal_content_caa =
|
||||
[ render_errors
|
||||
, Style.div_content [] [Style.explanation Explanations.caa_introduction]
|
||||
, side_text_for_name_input "domainCAA"
|
||||
, Style.input_with_side_text "domainCAA" "" "www"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input ("ttlCAA") "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
, Style.hr
|
||||
, Style.box_input ("flagCAA") "Flag" ""
|
||||
(updateForm Field_CAA_flag)
|
||||
(show (fromMaybe default_caa state._currentRR.caa).flag)
|
||||
, Style.selection_field'' "tagCAA" "Tag" CAA_tag (A.zip CAA.tags_txt CAA.tags_raw)
|
||||
CAA.Issue
|
||||
(Just (fromMaybe default_caa state._currentRR.caa).tag)
|
||||
, HH.div [HP.classes [C.notification, C.is_warning]]
|
||||
[ Style.p "⚠️ CAA entries aren't thoroughly verified, yet. Also, do not put quotes."
|
||||
]
|
||||
, Style.box_input "valueCAA" "Value" "" (updateForm Field_CAA_value)
|
||||
(fromMaybe default_caa state._currentRR.caa).value
|
||||
]
|
||||
modal_content_srv :: Array (HH.HTML w Action)
|
||||
modal_content_srv =
|
||||
[ Style.div_content [] [Style.explanation Explanations.srv_introduction]
|
||||
, render_errors
|
||||
, Style.box_input ("ttlSRV") "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
, Style.box_input "domainSRV" "Service name" "service name"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
, Style.selection_field "protocolSRV" "Protocol" SRV_Protocol RR.srv_protocols_txt
|
||||
(maybe "udp" (toLower <<< show) state._currentRR.protocol)
|
||||
, Style.box_input ("targetSRV") "Where the server is" "www"
|
||||
(updateForm Field_Target)
|
||||
state._currentRR.target
|
||||
, Style.box_input ("portSRV") "Port of the service" "5061"
|
||||
(updateForm Field_Port)
|
||||
(maybe "" show state._currentRR.port)
|
||||
, Style.div_content [] [Style.explanation [Style.p "The priority field is a numeric value that indicates the preference of the server (lower values indicate higher priority)."]]
|
||||
, Style.box_input ("prioritySRV") "Priority" "10"
|
||||
(updateForm Field_Priority)
|
||||
(maybe "" show state._currentRR.priority)
|
||||
-- Style.div_content [] [Style.explanation Explanations.spf_introduction], Style.p "Modifiers provide additional instructions, such as explanations for SPF failures or redirecting SPF checks to another domain."
|
||||
, Style.div_content [] [Style.explanation [Style.p "The weight field is a relative weight used when multiple servers have the same priority, determining how often they should be used."]]
|
||||
, Style.box_input ("weightSRV") "Weight" "100"
|
||||
(updateForm Field_Weight)
|
||||
(maybe "" show state._currentRR.weight)
|
||||
]
|
||||
modal_content_spf :: Array (HH.HTML w Action)
|
||||
modal_content_spf =
|
||||
[ Style.div_content [] [Style.explanation Explanations.spf_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainSPF"
|
||||
, Style.input_with_side_text "domainSPF" "" "Let this alone."
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input "ttlSPF" "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
--, case state._currentRR.v of
|
||||
-- Nothing -> Style.p "default value for the version (spf1)"
|
||||
-- Just v -> Style.box_input "vSPF" "Version" "spf1" (updateForm Field_SPF_v) v
|
||||
, Style.hr
|
||||
, Style.box_with_tag [C.has_background_info_light] tag_mechanisms
|
||||
[ Style.div_content [] [Style.explanation [Style.p "Mechanisms specify which mail servers are allowed to send mail for the domain and how to evaluate the sending mail server’s IP address."] ]
|
||||
, maybe (Style.p "You don't have any mechanism.") (Table.display_mechanisms SPF_remove_mechanism) state._currentRR.mechanisms
|
||||
, Style.hr
|
||||
, Style.h4 "New mechanism"
|
||||
, Style.selection_field "idMechanismQ" "Policy" SPF_Mechanism_q qualifier_types state.spf_mechanism_q
|
||||
, Style.selection_field "idMechanismT" "Type" SPF_Mechanism_t mechanism_types state.spf_mechanism_t
|
||||
, Style.box_input "valueNewMechanismSPF" "Value" ""
|
||||
SPF_Mechanism_v
|
||||
state.spf_mechanism_v
|
||||
, Style.btn "Add a mechanism" SPF_Mechanism_Add
|
||||
]
|
||||
, Style.hr
|
||||
, Style.box_with_tag [C.has_background_success_light] tag_modifiers
|
||||
[ Style.div_content [] [Style.explanation [Style.p "Modifiers provide additional instructions, such as explanations for SPF failures or redirecting SPF checks to another domain."] ]
|
||||
, maybe (Style.p "You don't have any modifier.") (Table.display_modifiers SPF_remove_modifier) state._currentRR.modifiers
|
||||
, Style.hr
|
||||
, Style.h4 "New modifier"
|
||||
, Style.selection_field "idModifierT" "Modifier" SPF_Modifier_t modifier_types state.spf_modifier_t
|
||||
, Style.box_input "valueNewModifierSPF" "Value" ""
|
||||
SPF_Modifier_v
|
||||
state.spf_modifier_v
|
||||
, Style.btn "Add a modifier" SPF_Modifier_Add
|
||||
]
|
||||
, Style.hr
|
||||
, Style.box
|
||||
[ Style.h3 "Default behavior"
|
||||
, Style.div_content [] [Style.explanation Explanations.spf_default_behavior]
|
||||
, Style.selection SPF_Qualifier qualifier_types (maybe default_qualifier_str show_qualifier state._currentRR.q)
|
||||
]
|
||||
]
|
||||
|
||||
tag_mechanisms = Style.tags [Style.tag "Mechanisms"]
|
||||
tag_modifiers = Style.tags [Style.tag "Modifiers"]
|
||||
|
||||
tag_aggregated_reports = Style.tags [Style.tag "Addresses to contact for aggregated reports"]
|
||||
tag_detailed_reports = Style.tags [Style.tag "Addresses to contact for detailed reports"]
|
||||
|
||||
modal_content_dkim :: Array (HH.HTML w Action)
|
||||
modal_content_dkim =
|
||||
[ Style.div_content [] [Style.explanation Explanations.dkim_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainDKIM"
|
||||
, Style.input_with_side_text "domainDKIM" "" "default._domainkey"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input "ttlDKIM" "TTL" "1800"
|
||||
(updateForm Field_TTL)
|
||||
(show state._currentRR.ttl)
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dkim_default_algorithms]
|
||||
, Style.selection_field "idDKIMSignature" "Signature algo"
|
||||
DKIM_sign_algo
|
||||
(map show DKIM.sign_algos)
|
||||
(show $ fromMaybe DKIM.RSA state.dkim.k)
|
||||
, Style.selection_field "idDKIMHash" "Hash algo"
|
||||
DKIM_hash_algo
|
||||
(map show DKIM.hash_algos)
|
||||
(show $ fromMaybe DKIM.SHA256 state.dkim.h)
|
||||
, Style.box_input "pkDKIM" "Public Key" "Your public key, such as \"MIIBIjANBgqh...\"" DKIM_pubkey state.dkim.p
|
||||
, Style.box_input "noteDKIM" "Note" "Note for fellow administrators." DKIM_note (fromMaybe "" state.dkim.n)
|
||||
]
|
||||
|
||||
modal_content_dmarc :: Array (HH.HTML w Action)
|
||||
modal_content_dmarc =
|
||||
[ Style.div_content [] [Style.explanation Explanations.dmarc_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainDMARC"
|
||||
, Style.input_with_side_text "domainDMARC" "" "_dmarc"
|
||||
(updateForm Field_Domain)
|
||||
state._currentRR.name
|
||||
display_domain_side
|
||||
, Style.box_input "ttlDMARC" "TTL" "1800" (updateForm Field_TTL) (show state._currentRR.ttl)
|
||||
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_policy]
|
||||
, Style.selection_field' "idDMARCPolicy" "Policy" DMARC_policy
|
||||
(A.zip DMARC.policies_txt DMARC.policies_raw)
|
||||
(show state.dmarc.p)
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_sp_policy]
|
||||
, Style.selection_field' "idDMARCPolicy_sp" "Policy for subdomains" DMARC_sp_policy
|
||||
(zip_nullable DMARC.policies_txt_with_null DMARC.policies_raw)
|
||||
(maybe "-" show state.dmarc.sp)
|
||||
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_adkim]
|
||||
, Style.selection_field' "idDMARCadkim" "Consistency Policy for DKIM" DMARC_adkim
|
||||
(zip_nullable DMARC.consistency_policies_txt DMARC.consistency_policies_raw)
|
||||
(maybe "-" show state.dmarc.adkim)
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_aspf]
|
||||
, Style.selection_field' "idDMARCaspf" "Consistency Policy for SPF" DMARC_aspf
|
||||
(zip_nullable DMARC.consistency_policies_txt DMARC.consistency_policies_raw)
|
||||
(maybe "-" show state.dmarc.aspf)
|
||||
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_pct]
|
||||
, Style.box_input "idDMARCpct" "Sample rate (between 0 and 100)" "100" DMARC_pct (maybe "100" show state.dmarc.pct)
|
||||
|
||||
, Style.hr
|
||||
, Style.selection_field' "idDMARCfo" "When to send a report" DMARC_fo
|
||||
(zip_nullable DMARC.report_occasions_txt DMARC.report_occasions_raw)
|
||||
(maybe "-" show state.dmarc.fo)
|
||||
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_contact]
|
||||
, Style.box_with_tag [C.has_background_info_light] tag_aggregated_reports
|
||||
[ maybe (Style.p "There is no address to send aggregated reports to.")
|
||||
(Table.display_dmarc_mail_addresses DMARC_remove_rua)
|
||||
state.dmarc.rua
|
||||
]
|
||||
, Style.box_with_tag [C.has_background_success_light] tag_detailed_reports
|
||||
[ maybe (Style.p "There is no address to send detailed reports to.")
|
||||
(Table.display_dmarc_mail_addresses DMARC_remove_ruf)
|
||||
state.dmarc.ruf
|
||||
]
|
||||
|
||||
, Style.hr
|
||||
, render_dmarc_mail_errors
|
||||
, Style.box_input "idDMARCmail" "Address to contact" "admin@example.com" DMARC_mail state.dmarc_mail
|
||||
, Style.box_input "idDMARCmaillimit" "Report size limit (in KB)" "2000" DMARC_mail_limit (maybe "0" show state.dmarc_mail_limit)
|
||||
, Style.level [ Style.btn_ [C.has_background_info_light] "New address for aggregated report" DMARC_rua_Add
|
||||
, Style.btn_ [C.has_background_success_light] "New address for specific report" DMARC_ruf_Add
|
||||
] []
|
||||
|
||||
, Style.hr
|
||||
, Style.div_content [] [Style.explanation Explanations.dmarc_ri]
|
||||
, Style.box_input "idDMARCri" "Report interval (in seconds)" "86400" DMARC_ri (maybe "0" show state.dmarc.ri)
|
||||
]
|
||||
|
||||
render_dmarc_mail_errors
|
||||
= if A.length state._dmarc_mail_errors > 0
|
||||
then Style.notification_danger_block'
|
||||
$ [ Style.h3 "Invalid mail 😥" ] <> map (Style.p <<< show_error_email) state._dmarc_mail_errors
|
||||
else HH.div_ [ ]
|
||||
|
||||
display_domain_side = (if state._currentRR.name == (state._domain <> ".") then "" else "." <> state._domain)
|
||||
newtokenbtn = Style.btn (maybe "🏁 Ask for a token" (\_ -> "🏁 Ask for a new token") state._currentRR.token) (NewToken state._currentRR.rrid)
|
||||
foot_content x =
|
||||
case state.rr_modal of
|
||||
NewRRModal _ -> [Style.btn_add (ValidateRR x)]
|
||||
UpdateRRModal -> [Style.btn_save ValidateLocal] <> case x of
|
||||
A -> [newtokenbtn]
|
||||
AAAA -> [newtokenbtn]
|
||||
_ -> []
|
||||
_ -> [Style.p "state.rr_modal should either be NewRRModal or UpdateRRModal."]
|
||||
template content foot_ = Style.modal title content foot
|
||||
where
|
||||
title = case state.rr_modal of
|
||||
NoModal -> "Error: no modal should be displayed"
|
||||
NewRRModal t_ -> "New " <> show t_ <> " resource record"
|
||||
UpdateRRModal -> "Update " <> state._currentRR.rrtype <> " Resource Record"
|
||||
RemoveRRModal rr_id -> "Error: should display removal modal instead (for resource record " <> show rr_id <> ")"
|
||||
foot = foot_ <> [Style.cancel_button CancelModal]
|
||||
|
||||
zip_nullable :: forall a. Array a -> Array String -> Array (Tuple a String)
|
||||
zip_nullable txt raw = A.zip txt ([""] <> raw)
|
||||
|
||||
handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
|
||||
handleAction = case _ of
|
||||
|
@ -684,7 +260,7 @@ handleAction = case _ of
|
|||
-- | Works for both "new RR", "update RR" and "remove RR" modals.
|
||||
CancelModal -> do
|
||||
H.modify_ _ { rr_modal = NoModal }
|
||||
H.modify_ _ { _currentRR_errors = [] }
|
||||
H.modify_ _ { _errors = [] }
|
||||
H.modify_ _ { _dmarc_mail_errors = [] }
|
||||
handleAction $ ResetTemporaryValues
|
||||
|
||||
|
@ -709,7 +285,7 @@ handleAction = case _ of
|
|||
case first (\rr -> rr.rrid == rr_id) state._resources of
|
||||
Nothing -> H.raise $ Log $ ErrorLog $ "Resource Record " <> show rr_id <> " not found"
|
||||
Just rr -> do
|
||||
H.modify_ _ { _currentRR = rr }
|
||||
H.modify_ _ { _rr_form { _rr = rr } }
|
||||
_ <- case rr.rrtype of
|
||||
"DKIM" -> H.modify_ _ { dkim = fromMaybe DKIM.emptyDKIMRR rr.dkim }
|
||||
"DMARC" -> H.modify_ _ { dmarc = fromMaybe DMARC.emptyDMARCRR rr.dmarc }
|
||||
|
@ -719,36 +295,7 @@ handleAction = case _ of
|
|||
-- | Each time a "new RR" button is clicked, the form resets.
|
||||
CreateNewRRModal t -> do
|
||||
state <- H.get
|
||||
H.modify_ _ { rr_modal = NewRRModal t }
|
||||
let default_rr_AAAA = emptyRR { rrtype = "AAAA", name = "server1", target = "2001:db8::1" }
|
||||
default_rr_TXT = emptyRR { rrtype = "TXT", name = "txt", target = "some text" }
|
||||
default_rr_CNAME = emptyRR { rrtype = "CNAME", name = "www", target = "server1" }
|
||||
default_rr_NS = emptyRR { rrtype = "NS", name = (state._domain <> "."), target = "ns0.example.com." }
|
||||
default_rr_MX = emptyRR { rrtype = "MX", name = "mail", target = "server1", priority = Just 10 }
|
||||
default_rr_CAA = emptyRR { rrtype = "CAA", name = "", caa = Just default_caa }
|
||||
default_rr_SRV = emptyRR { rrtype = "SRV", name = "voip", target = "server1"
|
||||
, port = Just 5061, weight = Just 100, priority = Just 10
|
||||
, protocol = Just RR.TCP }
|
||||
default_mechanisms = maybe [] (\x -> [x]) $ to_mechanism "pass" "mx" ""
|
||||
default_rr_SPF = emptyRR { rrtype = "SPF", name = "", target = ""
|
||||
, mechanisms = Just default_mechanisms
|
||||
, q = Just RR.HardFail
|
||||
}
|
||||
default_rr_DKIM = emptyRR { rrtype = "DKIM", name = "default._domainkey", target = "" }
|
||||
default_rr_DMARC = emptyRR { rrtype = "DMARC", name = "_dmarc", target = "" }
|
||||
|
||||
case t of
|
||||
A -> H.modify_ _ { _currentRR = default_rr_A }
|
||||
AAAA -> H.modify_ _ { _currentRR = default_rr_AAAA }
|
||||
TXT -> H.modify_ _ { _currentRR = default_rr_TXT }
|
||||
CNAME -> H.modify_ _ { _currentRR = default_rr_CNAME }
|
||||
NS -> H.modify_ _ { _currentRR = default_rr_NS }
|
||||
MX -> H.modify_ _ { _currentRR = default_rr_MX }
|
||||
CAA -> H.modify_ _ { _currentRR = default_rr_CAA }
|
||||
SRV -> H.modify_ _ { _currentRR = default_rr_SRV }
|
||||
SPF -> H.modify_ _ { _currentRR = default_rr_SPF }
|
||||
DKIM -> H.modify_ _ { _currentRR = default_rr_DKIM }
|
||||
DMARC -> H.modify_ _ { _currentRR = default_rr_DMARC }
|
||||
H.modify_ _ { rr_modal = NewRRModal t, _rr_form { _rr = default_rr t state._domain } }
|
||||
|
||||
-- | Initialize the Zone component: ask for the domain zone to `dnsmanagerd`.
|
||||
Initialize -> do
|
||||
|
@ -771,23 +318,23 @@ handleAction = case _ of
|
|||
-- | Else, the different errors are added to the state.
|
||||
ValidateRR t -> do
|
||||
-- In case the `name` part of the resource record is empty, consider the name to be the domain itself.
|
||||
H.modify_ \s -> s { _currentRR = replace_name s._domain s._currentRR }
|
||||
H.modify_ \s -> s { _rr_form { _rr = replace_name s._domain s._rr_form._rr } }
|
||||
|
||||
-- TODO: should the code design change? Would the code be simplified by working only on _currentRR.dkim?
|
||||
-- Since _currentRR.dkim isn't modified directly, it is copied from `State`.
|
||||
-- TODO: should the code design change? Would the code be simplified by working only on _rr_form._rr.dkim?
|
||||
-- Since _rr_form._rr.dkim isn't modified directly, it is copied from `State`.
|
||||
_ <- case t of
|
||||
DKIM -> H.modify_ \state -> state { _currentRR { dkim = Just state.dkim } }
|
||||
DMARC -> H.modify_ \state -> state { _currentRR { dmarc = Just state.dmarc } }
|
||||
DKIM -> H.modify_ \state -> state { _rr_form { _rr { dkim = Just state.dkim } } }
|
||||
DMARC -> H.modify_ \state -> state { _rr_form { _rr { dmarc = Just state.dmarc } } }
|
||||
_ -> pure unit
|
||||
|
||||
state <- H.get
|
||||
case Validation.validation state._currentRR of
|
||||
case Validation.validation state._rr_form._rr of
|
||||
Left actual_errors -> do
|
||||
-- H.raise $ Log $ ErrorLog $ "Cannot add this " <> show t <> " resource record, some errors occured in the record:"
|
||||
-- loopE (\v -> H.raise $ Log $ ErrorLog $ "==> " <> show_error v) actual_errors
|
||||
H.modify_ _ { _currentRR_errors = actual_errors }
|
||||
H.modify_ _ { _errors = actual_errors }
|
||||
Right newrr -> do
|
||||
H.modify_ _ { _currentRR_errors = []
|
||||
H.modify_ _ { _errors = []
|
||||
, _dmarc_mail_errors = []
|
||||
, dkim = DKIM.emptyDKIMRR
|
||||
, dmarc = DMARC.emptyDMARCRR
|
||||
|
@ -809,35 +356,35 @@ handleAction = case _ of
|
|||
-- | Update the currently displayed RR form (new or update RR).
|
||||
UpdateCurrentRR field -> do
|
||||
state <- H.get
|
||||
let newRR = update_field state._currentRR field
|
||||
H.modify_ _ { _currentRR = newRR }
|
||||
let newRR = update_field state._rr_form._rr field
|
||||
H.modify_ _ { _rr_form { _rr = newRR } }
|
||||
|
||||
-- | Validate any local RR with the new `_resources` and `_local_errors`.
|
||||
ValidateLocal -> do
|
||||
-- In case the `name` part of the resource record is empty, consider the name to be the domain itself.
|
||||
H.modify_ \s -> s { _currentRR = replace_name s._domain s._currentRR }
|
||||
H.modify_ \s -> s { _rr_form { _rr = replace_name s._domain s._rr_form._rr } }
|
||||
|
||||
-- Since _currentRR.dkim isn't modified directly, it is copied from `State`.
|
||||
-- Since _rr_form._rr.dkim isn't modified directly, it is copied from `State`.
|
||||
state0 <- H.get
|
||||
_ <- case state0._currentRR.rrtype of
|
||||
"DKIM" -> H.modify_ _ { _currentRR { dkim = Just state0.dkim } }
|
||||
"DMARC" -> H.modify_ _ { _currentRR { dmarc = Just state0.dmarc } }
|
||||
_ <- case state0._rr_form._rr.rrtype of
|
||||
"DKIM" -> H.modify_ _ { _rr_form { _rr { dkim = Just state0.dkim } } }
|
||||
"DMARC" -> H.modify_ _ { _rr_form { _rr { dmarc = Just state0.dmarc } } }
|
||||
_ -> pure unit
|
||||
|
||||
state <- H.get
|
||||
case Validation.validation state._currentRR of
|
||||
case Validation.validation state._rr_form._rr of
|
||||
Left actual_errors -> do
|
||||
H.modify_ _ { _currentRR_errors = actual_errors }
|
||||
H.modify_ _ { _errors = actual_errors }
|
||||
Right rr -> do
|
||||
H.modify_ _ { _currentRR_errors = [], _dmarc_mail_errors = [] }
|
||||
H.modify_ _ { _errors = [], _dmarc_mail_errors = [] }
|
||||
handleAction $ SaveRR rr
|
||||
|
||||
ResetTemporaryValues -> do
|
||||
H.modify_ _ { spf_mechanism_q = "pass"
|
||||
, spf_mechanism_t = "a"
|
||||
, spf_mechanism_v = ""
|
||||
, spf_modifier_t = "redirect"
|
||||
, spf_modifier_v = ""
|
||||
H.modify_ _ { _rr_form { tmp { spf { mechanism_q = "pass" } } }
|
||||
, _rr_form { tmp { spf { mechanism_t = "a" } } }
|
||||
, _rr_form { tmp { spf { mechanism_v = "" } } }
|
||||
, _rr_form { tmp { spf { modifier_t = "redirect" } } }
|
||||
, _rr_form { tmp { spf { modifier_v = "" } } }
|
||||
, dmarc_mail = ""
|
||||
, dmarc_mail_limit = Nothing
|
||||
, _dmarc_mail_errors = []
|
||||
|
@ -890,53 +437,51 @@ handleAction = case _ of
|
|||
CAA.ContactEmail -> "contact@example.com"
|
||||
CAA.ContactPhone -> "0203040506"
|
||||
_ -> ""
|
||||
new_caa = (fromMaybe default_caa state._currentRR.caa) { tag = new_tag, value = new_value }
|
||||
H.modify_ _ { _currentRR { caa = Just new_caa } }
|
||||
new_caa = (fromMaybe default_caa state._rr_form._rr.caa) { tag = new_tag, value = new_value }
|
||||
H.modify_ _ { _rr_form { _rr { caa = Just new_caa } } }
|
||||
|
||||
SRV_Protocol v -> H.modify_ _ { _currentRR { protocol = RR.srv_protocols A.!! v } }
|
||||
SRV_Protocol v -> H.modify_ _ { _rr_form { _rr { protocol = RR.srv_protocols A.!! v } } }
|
||||
|
||||
SPF_Mechanism_q v -> H.modify_ _ { spf_mechanism_q = maybe "pass" id $ qualifier_types A.!! v }
|
||||
SPF_Mechanism_t v -> H.modify_ _ { spf_mechanism_t = maybe "a" id $ mechanism_types A.!! v }
|
||||
SPF_Mechanism_v v -> H.modify_ _ { spf_mechanism_v = v }
|
||||
SPF_Modifier_t v -> H.modify_ _ { spf_modifier_t = maybe "redirect" id $ modifier_types A.!! v }
|
||||
SPF_Modifier_v v -> H.modify_ _ { spf_modifier_v = v }
|
||||
SPF_Qualifier v -> H.modify_ _ { _currentRR { q = qualifiers A.!! v } }
|
||||
SPF_Mechanism_q v -> H.modify_ _ { _rr_form { tmp { spf { mechanism_q = maybe "pass" id $ qualifier_types A.!! v }}}
|
||||
SPF_Mechanism_t v -> H.modify_ _ { _rr_form { tmp { spf { mechanism_t = maybe "a" id $ mechanism_types A.!! v }}}
|
||||
SPF_Mechanism_v v -> H.modify_ _ { _rr_form { tmp { spf { mechanism_v = v }}}
|
||||
SPF_Modifier_t v -> H.modify_ _ { _rr_form { tmp { spf { modifier_t = maybe "redirect" id $ modifier_types A.!! v }}}
|
||||
SPF_Modifier_v v -> H.modify_ _ { _rr_form { tmp { spf { modifier_v = v }}}
|
||||
SPF_Qualifier v -> H.modify_ _ { _rr_form { _rr { q = qualifiers A.!! v } }
|
||||
SPF_remove_mechanism i ->
|
||||
H.modify_ \s -> s { _currentRR { mechanisms = case s._currentRR.mechanisms of
|
||||
Just ms -> Just (remove_id i $ attach_id 0 ms)
|
||||
Nothing -> Nothing
|
||||
}
|
||||
}
|
||||
H.modify_ \s -> s { _rr_form { _rr { mechanisms = case s._rr_form._rr.mechanisms of
|
||||
Just ms -> Just (remove_id i $ attach_id 0 ms)
|
||||
Nothing -> Nothing
|
||||
} } }
|
||||
SPF_remove_modifier i ->
|
||||
H.modify_ \s -> s { _currentRR { modifiers = case s._currentRR.modifiers of
|
||||
Just ms -> Just (remove_id i $ attach_id 0 ms)
|
||||
Nothing -> Nothing
|
||||
}
|
||||
}
|
||||
H.modify_ \s -> s { _rr_form { _rr { modifiers = case s._rr_form._rr.modifiers of
|
||||
Just ms -> Just (remove_id i $ attach_id 0 ms)
|
||||
Nothing -> Nothing
|
||||
} } }
|
||||
|
||||
SPF_Mechanism_Add -> do
|
||||
state <- H.get
|
||||
let m = state._currentRR.mechanisms
|
||||
m_q = state.spf_mechanism_q
|
||||
m_t = state.spf_mechanism_t
|
||||
m_v = state.spf_mechanism_v
|
||||
let m = state._rr_form._rr.mechanisms
|
||||
m_q = state._rr_form.tmp.spf.mechanism_q
|
||||
m_t = state._rr_form.tmp.spf.mechanism_t
|
||||
m_v = state._rr_form.tmp.spf.mechanism_v
|
||||
new_list_of_mechanisms = maybe [] id m <> maybe [] (\x -> [x]) (to_mechanism m_q m_t m_v)
|
||||
new_value = case new_list_of_mechanisms of
|
||||
[] -> Nothing
|
||||
v -> Just v
|
||||
H.modify_ _ { _currentRR { mechanisms = new_value }}
|
||||
H.modify_ _ { _rr_form { _rr { mechanisms = new_value }}}
|
||||
handleAction $ ResetTemporaryValues
|
||||
|
||||
SPF_Modifier_Add -> do
|
||||
state <- H.get
|
||||
let m = state._currentRR.modifiers
|
||||
m_t = state.spf_modifier_t
|
||||
m_v = state.spf_modifier_v
|
||||
let m = state._rr_form._rr.modifiers
|
||||
m_t = state._rr_form.tmp.spf.modifier_t
|
||||
m_v = state._rr_form.tmp.spf.modifier_v
|
||||
new_list_of_modifiers = maybe [] id m <> maybe [] (\x -> [x]) (to_modifier m_t m_v)
|
||||
new_value = case new_list_of_modifiers of
|
||||
[] -> Nothing
|
||||
v -> Just v
|
||||
H.modify_ _ { _currentRR { modifiers = new_value }}
|
||||
H.modify_ _ { _rr_form._rr { modifiers = new_value }}
|
||||
handleAction $ ResetTemporaryValues
|
||||
|
||||
DMARC_mail v -> H.modify_ _ { dmarc_mail = v }
|
||||
|
@ -1046,41 +591,37 @@ handleQuery = case _ of
|
|||
add_RR :: State -> ResourceRecord -> State
|
||||
add_RR state new_rr = state { _resources = (state._resources <> [ new_rr ]) }
|
||||
|
||||
-- Rendering
|
||||
|
||||
-- Component definition and initial state
|
||||
|
||||
render_new_records :: forall (w :: Type). State -> HH.HTML w Action
|
||||
render_new_records _
|
||||
= Style.hdiv
|
||||
[ Style.h1 "Adding new records"
|
||||
= Web.hdiv
|
||||
[ Web.h1 "Adding new records"
|
||||
-- use "level" to get horizontal buttons next to each other (probably vertical on mobile)
|
||||
, Style.level [
|
||||
Style.btn "A" (CreateNewRRModal A)
|
||||
, Style.btn "AAAA" (CreateNewRRModal AAAA)
|
||||
, Style.btn "TXT" (CreateNewRRModal TXT)
|
||||
, Style.btn "CNAME" (CreateNewRRModal CNAME)
|
||||
, Style.btn "NS" (CreateNewRRModal NS)
|
||||
, Style.btn "MX" (CreateNewRRModal MX)
|
||||
, Style.btn "SRV" (CreateNewRRModal SRV)
|
||||
, Web.level [
|
||||
Web.btn "A" (CreateNewRRModal A)
|
||||
, Web.btn "AAAA" (CreateNewRRModal AAAA)
|
||||
, Web.btn "TXT" (CreateNewRRModal TXT)
|
||||
, Web.btn "CNAME" (CreateNewRRModal CNAME)
|
||||
, Web.btn "NS" (CreateNewRRModal NS)
|
||||
, Web.btn "MX" (CreateNewRRModal MX)
|
||||
, Web.btn "SRV" (CreateNewRRModal SRV)
|
||||
] []
|
||||
, Style.hr
|
||||
, Style.h1 "Special records about certifications and the mail system"
|
||||
, Web.hr
|
||||
, Web.h1 "Special records about certifications and the mail system"
|
||||
-- use "level" to get horizontal buttons next to each other (probably vertical on mobile)
|
||||
, Style.level [
|
||||
Style.btn "CAA" (CreateNewRRModal CAA)
|
||||
, Style.btn "SPF" (CreateNewRRModal SPF)
|
||||
, Style.btn "DKIM" (CreateNewRRModal DKIM)
|
||||
, Style.btn "DMARC" (CreateNewRRModal DMARC)
|
||||
, Web.level [
|
||||
Web.btn "CAA" (CreateNewRRModal CAA)
|
||||
, Web.btn "SPF" (CreateNewRRModal SPF)
|
||||
, Web.btn "DKIM" (CreateNewRRModal DKIM)
|
||||
, Web.btn "DMARC" (CreateNewRRModal DMARC)
|
||||
] []
|
||||
, Style.hr
|
||||
, Style.level [
|
||||
Style.btn "Get the final zone file" AskZoneFile
|
||||
, Web.hr
|
||||
, Web.level [
|
||||
Web.btn "Get the final zone file" AskZoneFile
|
||||
] [HH.text "For debug purposes. ⚠"]
|
||||
]
|
||||
|
||||
render_zonefile :: forall (w :: Type). Maybe String -> HH.HTML w Action
|
||||
render_zonefile zonefile = Style.box [ maybe (HH.text "") (\x -> HH.pre_ [HH.text x]) zonefile ]
|
||||
render_zonefile zonefile = Web.box [ maybe (HH.text "") (\x -> HH.pre_ [HH.text x]) zonefile ]
|
||||
|
||||
-- ACTIONS
|
||||
|
||||
|
@ -1098,23 +639,23 @@ loopE f a = case (A.head a) of
|
|||
Nothing -> pure unit
|
||||
Just xs -> loopE f xs
|
||||
|
||||
update_field :: ResourceRecord -> Field -> ResourceRecord
|
||||
update_field :: ResourceRecord -> Field.Field -> ResourceRecord
|
||||
update_field rr updated_field = case updated_field of
|
||||
Field_Domain val -> rr { name = toLower val }
|
||||
Field_Target val -> rr { target = val }
|
||||
Field_TTL val -> rr { ttl = fromMaybe 0 (fromString val) }
|
||||
Field_Priority val -> rr { priority = fromString val }
|
||||
Field_Weight val -> rr { weight = fromString val }
|
||||
Field_Port val -> rr { port = fromString val }
|
||||
Field_SPF_v val -> rr { v = Just val }
|
||||
Field_SPF_mechanisms val -> rr { mechanisms = Just val }
|
||||
Field_SPF_modifiers val -> rr { modifiers = Just val }
|
||||
Field_SPF_q val -> rr { q = Just val }
|
||||
Field.Domain val -> rr { name = toLower val }
|
||||
Field.Target val -> rr { target = val }
|
||||
Field.TTL val -> rr { ttl = fromMaybe 0 (fromString val) }
|
||||
Field.Priority val -> rr { priority = fromString val }
|
||||
Field.Weight val -> rr { weight = fromString val }
|
||||
Field.Port val -> rr { port = fromString val }
|
||||
Field.SPF_v val -> rr { v = Just val }
|
||||
Field.SPF_mechanisms val -> rr { mechanisms = Just val }
|
||||
Field.SPF_modifiers val -> rr { modifiers = Just val }
|
||||
Field.SPF_q val -> rr { q = Just val }
|
||||
|
||||
Field_CAA_flag val ->
|
||||
Field.CAA_flag val ->
|
||||
let new_caa = (fromMaybe default_caa rr.caa) { flag = fromMaybe 0 $ fromString val }
|
||||
in rr { caa = Just new_caa }
|
||||
|
||||
Field_CAA_value val ->
|
||||
Field.CAA_value val ->
|
||||
let new_caa = (fromMaybe default_caa rr.caa) { value = val }
|
||||
in rr { caa = Just new_caa }
|
||||
|
|
375
src/App/Templates/Modal.purs
Normal file
375
src/App/Templates/Modal.purs
Normal file
|
@ -0,0 +1,375 @@
|
|||
-- | `App.Templates.Modal` gathers all the website's modals, providing
|
||||
-- | an easy way to duplicate modals in different pages and to display
|
||||
-- | content in a consistent manner.
|
||||
module App.Templates.Modal where
|
||||
|
||||
import Prelude (map, show, ($), (<<<), (<>), (==), (>))
|
||||
|
||||
import CSSClasses as C
|
||||
|
||||
import Data.Array as A
|
||||
import Data.Maybe (Maybe(..), fromMaybe, maybe)
|
||||
|
||||
import Data.Tuple (Tuple)
|
||||
|
||||
import App.Type.CAA as CAA
|
||||
import App.Text.Explanations as Explanations
|
||||
import Web as Web
|
||||
import Halogen.HTML as HH
|
||||
import Halogen.HTML.Properties as HP
|
||||
import App.Type.RRId (RRId)
|
||||
import App.Type.DMARC as DMARC
|
||||
import App.Type.DKIM as DKIM
|
||||
import App.Type.Field as Field
|
||||
import App.Templates.Table as Table
|
||||
import Data.String (toLower)
|
||||
|
||||
-- FIXME: this import is related to messy types. A replacement should be found.
|
||||
import App.Type.RRForm
|
||||
|
||||
import App.Type.RRModal (RRModal(..))
|
||||
import App.Type.AcceptedRRTypes (AcceptedRRTypes(..))
|
||||
|
||||
import App.Type.ResourceRecord (mechanism_types, modifier_types, qualifier_types, show_qualifier)
|
||||
import App.Type.ResourceRecord as RR
|
||||
|
||||
import App.DisplayErrors (error_to_paragraph, show_error_email)
|
||||
|
||||
type ActionCancelModal = forall i. i
|
||||
modal_rr_delete :: forall w i. Int -> (Int -> i) -> ActionCancelModal -> HH.HTML w i
|
||||
modal_rr_delete rr_id action_remove_rr action_cancel_modal = Web.modal "Deleting a resource record"
|
||||
[warning_message] [modal_delete_button, Web.cancel_button action_cancel_modal]
|
||||
where
|
||||
modal_delete_button = Web.alert_btn "Delete the resource record" (action_remove_rr rr_id)
|
||||
warning_message
|
||||
= HH.p [] [ HH.text "You are about to delete a resource record, this action is "
|
||||
, Web.strong "irreversible"
|
||||
, HH.text "."
|
||||
]
|
||||
|
||||
zip_nullable :: forall a. Array a -> Array String -> Array (Tuple a String)
|
||||
zip_nullable txt raw = A.zip txt ([""] <> raw)
|
||||
|
||||
type Domain = String
|
||||
type ActionUpdateForm = forall i. (Field.Field -> i)
|
||||
type ActionNewToken = forall i. (RRId -> i)
|
||||
type ActionUpdateRR = forall i. (RRUpdateValue -> i)
|
||||
type ActionValidateNewRR = forall i. (AcceptedRRTypes -> i)
|
||||
type ActionValidateLocalRR = forall i. i
|
||||
current_rr_modal :: forall w i.
|
||||
Domain -> RRForm -> RRModal
|
||||
-> ActionUpdateForm -> ActionNewToken
|
||||
-> ActionUpdateRR -> ActionValidateNewRR -> ActionValidateLocalRR -> ActionCancelModal
|
||||
-> HH.HTML w i
|
||||
current_rr_modal selected_domain form rr_modal
|
||||
action_update_form action_new_token
|
||||
action_update_rr action_validate_rr action_validate_local_rr action_cancel_modal =
|
||||
case form._rr.rrtype of
|
||||
"A" -> template (modal_content_simple A) (foot_content A)
|
||||
"AAAA" -> template (modal_content_simple AAAA) (foot_content AAAA)
|
||||
"TXT" -> template (modal_content_simple TXT) (foot_content TXT)
|
||||
"CNAME" -> template (modal_content_simple CNAME) (foot_content CNAME)
|
||||
"NS" -> template (modal_content_simple NS) (foot_content NS)
|
||||
"MX" -> template modal_content_mx (foot_content MX)
|
||||
"CAA" -> template modal_content_caa (foot_content CAA)
|
||||
"SRV" -> template modal_content_srv (foot_content SRV)
|
||||
"SPF" -> template modal_content_spf (foot_content SPF)
|
||||
"DKIM" -> template modal_content_dkim (foot_content DKIM)
|
||||
"DMARC" -> template modal_content_dmarc (foot_content DMARC)
|
||||
_ -> Web.p $ "Invalid type: " <> form._rr.rrtype
|
||||
where
|
||||
side_text_for_name_input name_id
|
||||
= Web.side_text_above_input name_id "Name" (HH.text $ "Empty name = root domain (" <> selected_domain <> ".)")
|
||||
|
||||
-- DRY
|
||||
render_errors = if A.length form._errors > 0
|
||||
then HH.div_ $ [ Web.h3 "Errors: " ] <> map error_to_paragraph form._errors
|
||||
else HH.div_ [ ]
|
||||
|
||||
modal_content_simple :: AcceptedRRTypes -> Array (HH.HTML w i)
|
||||
modal_content_simple x =
|
||||
[ render_errors
|
||||
, render_introduction_text x
|
||||
, side_text_for_name_input ("domain" <> form._rr.rrtype)
|
||||
, Web.input_with_side_text ("domain" <> form._rr.rrtype) "" "www"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input ("ttl" <> form._rr.rrtype) "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
, case form._rr.rrtype of
|
||||
"AAAA" -> Web.box_input ("target" <> form._rr.rrtype) "Target" "2001:db8::1" (action_update_form <<< Field.Target) form._rr.target
|
||||
"TXT" -> Web.box_input ("target" <> form._rr.rrtype) "Your text" "blah blah" (action_update_form <<< Field.Target) form._rr.target
|
||||
"CNAME" -> Web.box_input ("target" <> form._rr.rrtype) "Target" "www" (action_update_form <<< Field.Target) form._rr.target
|
||||
"NS" -> Web.box_input ("target" <> form._rr.rrtype) "Target" "ns0.example.com." (action_update_form <<< Field.Target) form._rr.target
|
||||
_ -> Web.box_input ("target" <> form._rr.rrtype) "Target" "198.51.100.5" (action_update_form <<< Field.Target) form._rr.target
|
||||
] <> case rr_modal of
|
||||
UpdateRRModal ->
|
||||
if A.elem form._rr.rrtype ["A", "AAAA"]
|
||||
then [ Web.field_entry ("token" <> form._rr.rrtype) "Token"
|
||||
(maybe (Web.text "❌") Web.p form._rr.token)
|
||||
]
|
||||
else []
|
||||
_ -> []
|
||||
|
||||
render_introduction_text :: AcceptedRRTypes -> HH.HTML w i
|
||||
render_introduction_text = case _ of
|
||||
A -> Web.div_content [] [Web.explanation Explanations.a_introduction]
|
||||
AAAA -> Web.div_content [] [Web.explanation Explanations.aaaa_introduction]
|
||||
TXT -> Web.div_content [] [Web.explanation Explanations.txt_introduction]
|
||||
CNAME -> Web.div_content [] [Web.explanation Explanations.cname_introduction]
|
||||
NS -> Web.div_content [] [Web.explanation Explanations.ns_introduction]
|
||||
_ -> HH.p_ []
|
||||
|
||||
modal_content_mx :: Array (HH.HTML w i)
|
||||
modal_content_mx =
|
||||
[ render_errors
|
||||
, Web.div_content [] [Web.explanation Explanations.mx_introduction]
|
||||
, side_text_for_name_input "domainMX"
|
||||
, Web.input_with_side_text "domainMX" "" "www"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input ("ttlMX") "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
, Web.box_input ("targetMX") "Target" "www"
|
||||
(action_update_form <<< Field.Target)
|
||||
form._rr.target
|
||||
, Web.box_input ("priorityMX") "Priority" "10"
|
||||
(action_update_form <<< Field.Priority)
|
||||
(maybe "" show form._rr.priority)
|
||||
]
|
||||
|
||||
modal_content_caa :: Array (HH.HTML w i)
|
||||
modal_content_caa =
|
||||
[ render_errors
|
||||
, Web.div_content [] [Web.explanation Explanations.caa_introduction]
|
||||
, side_text_for_name_input "domainCAA"
|
||||
, Web.input_with_side_text "domainCAA" "" "www"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input ("ttlCAA") "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
, Web.hr
|
||||
, Web.box_input ("flagCAA") "Flag" ""
|
||||
(action_update_form <<< Field.CAA_flag)
|
||||
(show (fromMaybe default_caa form._rr.caa).flag)
|
||||
, Web.selection_field'' "tagCAA" "Tag" (action_update_rr <<< CAA_tag) (A.zip CAA.tags_txt CAA.tags_raw)
|
||||
CAA.Issue
|
||||
(Just (fromMaybe default_caa form._rr.caa).tag)
|
||||
, HH.div [HP.classes [C.notification, C.is_warning]]
|
||||
[ Web.p "⚠️ CAA entries aren't thoroughly verified, yet. Also, do not put quotes."
|
||||
]
|
||||
, Web.box_input "valueCAA" "Value" "" (action_update_form <<< Field.CAA_value)
|
||||
(fromMaybe default_caa form._rr.caa).value
|
||||
]
|
||||
|
||||
modal_content_srv :: Array (HH.HTML w i)
|
||||
modal_content_srv =
|
||||
[ Web.div_content [] [Web.explanation Explanations.srv_introduction]
|
||||
, render_errors
|
||||
, Web.box_input ("ttlSRV") "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
, Web.box_input "domainSRV" "Service name" "service name"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
, Web.selection_field "protocolSRV" "Protocol" (action_update_rr <<< SRV_Protocol) RR.srv_protocols_txt
|
||||
(maybe "udp" (toLower <<< show) form._rr.protocol)
|
||||
, Web.box_input ("targetSRV") "Where the server is" "www"
|
||||
(action_update_form <<< Field.Target)
|
||||
form._rr.target
|
||||
, Web.box_input ("portSRV") "Port of the service" "5061"
|
||||
(action_update_form <<< Field.Port)
|
||||
(maybe "" show form._rr.port)
|
||||
, Web.div_content [] [Web.explanation [Web.p "The priority field is a numeric value that indicates the preference of the server (lower values indicate higher priority)."]]
|
||||
, Web.box_input ("prioritySRV") "Priority" "10"
|
||||
(action_update_form <<< Field.Priority)
|
||||
(maybe "" show form._rr.priority)
|
||||
-- Web.div_content [] [Web.explanation Explanations.spf_introduction], Web.p "Modifiers provide additional instructions, such as explanations for SPF failures or redirecting SPF checks to another domain."
|
||||
, Web.div_content [] [Web.explanation [Web.p "The weight field is a relative weight used when multiple servers have the same priority, determining how often they should be used."]]
|
||||
, Web.box_input ("weightSRV") "Weight" "100"
|
||||
(action_update_form <<< Field.Weight)
|
||||
(maybe "" show form._rr.weight)
|
||||
]
|
||||
|
||||
modal_content_spf :: Array (HH.HTML w i)
|
||||
modal_content_spf =
|
||||
[ Web.div_content [] [Web.explanation Explanations.spf_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainSPF"
|
||||
, Web.input_with_side_text "domainSPF" "" "Let this alone."
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input "ttlSPF" "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
--, case form._rr.v of
|
||||
-- Nothing -> Web.p "default value for the version (spf1)"
|
||||
-- Just v -> Web.box_input "vSPF" "Version" "spf1" (action_update_form <<< Field.SPF_v) v
|
||||
, Web.hr
|
||||
, Web.box_with_tag [C.has_background_info_light] tag_mechanisms
|
||||
[ Web.div_content [] [Web.explanation [Web.p "Mechanisms specify which mail servers are allowed to send mail for the domain and how to evaluate the sending mail server’s IP address."] ]
|
||||
, maybe (Web.p "You don't have any mechanism.") (Table.display_mechanisms (action_update_rr <<< SPF_remove_mechanism)) form._rr.mechanisms
|
||||
, Web.hr
|
||||
, Web.h4 "New mechanism"
|
||||
, Web.selection_field "idMechanismQ" "Policy" (action_update_rr <<< SPF_Mechanism_q) qualifier_types form.tmp.spf.mechanism_q
|
||||
, Web.selection_field "idMechanismT" "Type" (action_update_rr <<< SPF_Mechanism_t) mechanism_types form.tmp.spf.mechanism_t
|
||||
, Web.box_input "valueNewMechanismSPF" "Value" ""
|
||||
(action_update_rr <<< SPF_Mechanism_v)
|
||||
form.tmp.spf.mechanism_v
|
||||
, Web.btn "Add a mechanism" (action_update_rr SPF_Mechanism_Add)
|
||||
]
|
||||
, Web.hr
|
||||
, Web.box_with_tag [C.has_background_success_light] tag_modifiers
|
||||
[ Web.div_content [] [Web.explanation [Web.p "Modifiers provide additional instructions, such as explanations for SPF failures or redirecting SPF checks to another domain."] ]
|
||||
, maybe (Web.p "You don't have any modifier.") (Table.display_modifiers (action_update_rr <<< SPF_remove_modifier)) form._rr.modifiers
|
||||
, Web.hr
|
||||
, Web.h4 "New modifier"
|
||||
, Web.selection_field "idModifierT" "Modifier" (action_update_rr <<< SPF_Modifier_t) modifier_types form.tmp.spf.modifier_t
|
||||
, Web.box_input "valueNewModifierSPF" "Value" ""
|
||||
(action_update_rr <<< SPF_Modifier_v)
|
||||
form.tmp.spf.modifier_v
|
||||
, Web.btn "Add a modifier" (action_update_rr SPF_Modifier_Add)
|
||||
]
|
||||
, Web.hr
|
||||
, Web.box
|
||||
[ Web.h3 "Default behavior"
|
||||
, Web.div_content [] [Web.explanation Explanations.spf_default_behavior]
|
||||
, Web.selection (action_update_rr <<< SPF_Qualifier) qualifier_types (maybe default_qualifier_str show_qualifier form._rr.q)
|
||||
]
|
||||
]
|
||||
|
||||
tag_mechanisms = Web.tags [Web.tag "Mechanisms"]
|
||||
tag_modifiers = Web.tags [Web.tag "Modifiers"]
|
||||
|
||||
tag_aggregated_reports = Web.tags [Web.tag "Addresses to contact for aggregated reports"]
|
||||
tag_detailed_reports = Web.tags [Web.tag "Addresses to contact for detailed reports"]
|
||||
|
||||
modal_content_dkim :: Array (HH.HTML w i)
|
||||
modal_content_dkim =
|
||||
[ Web.div_content [] [Web.explanation Explanations.dkim_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainDKIM"
|
||||
, Web.input_with_side_text "domainDKIM" "" "default._domainkey"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input "ttlDKIM" "TTL" "1800"
|
||||
(action_update_form <<< Field.TTL)
|
||||
(show form._rr.ttl)
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dkim_default_algorithms]
|
||||
, Web.selection_field "idDKIMSignature" "Signature algo"
|
||||
(action_update_rr <<< DKIM_sign_algo)
|
||||
(map show DKIM.sign_algos)
|
||||
(show $ fromMaybe DKIM.RSA form.tmp.dkim.k)
|
||||
, Web.selection_field "idDKIMHash" "Hash algo"
|
||||
(action_update_rr <<< DKIM_hash_algo)
|
||||
(map show DKIM.hash_algos)
|
||||
(show $ fromMaybe DKIM.SHA256 form.tmp.dkim.h)
|
||||
, Web.box_input "pkDKIM" "Public Key" "Your public key, such as \"MIIBIjANBgqh...\"" (action_update_rr <<< DKIM_pubkey) form.tmp.dkim.p
|
||||
, Web.box_input "noteDKIM" "Note" "Note for fellow administrators." (action_update_rr <<< DKIM_note) (fromMaybe "" form.tmp.dkim.n)
|
||||
]
|
||||
|
||||
modal_content_dmarc :: Array (HH.HTML w i)
|
||||
modal_content_dmarc =
|
||||
[ Web.div_content [] [Web.explanation Explanations.dmarc_introduction]
|
||||
, render_errors
|
||||
, side_text_for_name_input "domainDMARC"
|
||||
, Web.input_with_side_text "domainDMARC" "" "_dmarc"
|
||||
(action_update_form <<< Field.Domain)
|
||||
form._rr.name
|
||||
display_domain_side
|
||||
, Web.box_input "ttlDMARC" "TTL" "1800" (action_update_form <<< Field.TTL) (show form._rr.ttl)
|
||||
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_policy]
|
||||
, Web.selection_field' "idDMARCPolicy" "Policy" (action_update_rr <<< DMARC_policy)
|
||||
(A.zip DMARC.policies_txt DMARC.policies_raw)
|
||||
(show form.tmp.dmarc.p)
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_sp_policy]
|
||||
, Web.selection_field' "idDMARCPolicy_sp" "Policy for subdomains" (action_update_rr <<< DMARC_sp_policy)
|
||||
(zip_nullable DMARC.policies_txt_with_null DMARC.policies_raw)
|
||||
(maybe "-" show form.tmp.dmarc.sp)
|
||||
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_adkim]
|
||||
, Web.selection_field' "idDMARCadkim" "Consistency Policy for DKIM" (action_update_rr <<< DMARC_adkim)
|
||||
(zip_nullable DMARC.consistency_policies_txt DMARC.consistency_policies_raw)
|
||||
(maybe "-" show form.tmp.dmarc.adkim)
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_aspf]
|
||||
, Web.selection_field' "idDMARCaspf" "Consistency Policy for SPF" (action_update_rr <<< DMARC_aspf)
|
||||
(zip_nullable DMARC.consistency_policies_txt DMARC.consistency_policies_raw)
|
||||
(maybe "-" show form.tmp.dmarc.aspf)
|
||||
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_pct]
|
||||
, Web.box_input "idDMARCpct" "Sample rate (between 0 and 100)" "100" (action_update_rr <<< DMARC_pct) (maybe "100" show form.tmp.dmarc.pct)
|
||||
|
||||
, Web.hr
|
||||
, Web.selection_field' "idDMARCfo" "When to send a report" (action_update_rr <<< DMARC_fo)
|
||||
(zip_nullable DMARC.report_occasions_txt DMARC.report_occasions_raw)
|
||||
(maybe "-" show form.tmp.dmarc.fo)
|
||||
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_contact]
|
||||
, Web.box_with_tag [C.has_background_info_light] tag_aggregated_reports
|
||||
[ maybe (Web.p "There is no address to send aggregated reports to.")
|
||||
(Table.display_dmarc_mail_addresses (action_update_rr <<< DMARC_remove_rua))
|
||||
form.tmp.dmarc.rua
|
||||
]
|
||||
, Web.box_with_tag [C.has_background_success_light] tag_detailed_reports
|
||||
[ maybe (Web.p "There is no address to send detailed reports to.")
|
||||
(Table.display_dmarc_mail_addresses (action_update_rr <<< DMARC_remove_ruf))
|
||||
form.tmp.dmarc.ruf
|
||||
]
|
||||
|
||||
, Web.hr
|
||||
, render_dmarc_mail_errors
|
||||
, Web.box_input "idDMARCmail" "Address to contact" "admin@example.com" (action_update_rr <<< DMARC_mail) form.tmp.dmarc_mail
|
||||
, Web.box_input "idDMARCmaillimit" "Report size limit (in KB)" "2000" (action_update_rr <<< DMARC_mail_limit) (maybe "0" show form.tmp.dmarc_mail_limit)
|
||||
, Web.level [ Web.btn_ [C.has_background_info_light] "New address for aggregated report" (action_update_rr DMARC_rua_Add)
|
||||
, Web.btn_ [C.has_background_success_light] "New address for specific report" (action_update_rr DMARC_ruf_Add)
|
||||
] []
|
||||
|
||||
, Web.hr
|
||||
, Web.div_content [] [Web.explanation Explanations.dmarc_ri]
|
||||
, Web.box_input "idDMARCri" "Report interval (in seconds)" "86400" (action_update_rr <<< DMARC_ri) (maybe "0" show form.tmp.dmarc.ri)
|
||||
]
|
||||
|
||||
render_dmarc_mail_errors
|
||||
= if A.length form._dmarc_mail_errors > 0
|
||||
then Web.notification_danger_block'
|
||||
$ [ Web.h3 "Invalid mail 😥" ] <> map (Web.p <<< show_error_email) form._dmarc_mail_errors
|
||||
else HH.div_ [ ]
|
||||
|
||||
display_domain_side = (if form._rr.name == (selected_domain <> ".") then "" else "." <> selected_domain)
|
||||
|
||||
newtokenbtn :: HH.HTML w i
|
||||
newtokenbtn = Web.btn (maybe "🏁 Ask for a token" (\_ -> "🏁 Ask for a new token") form._rr.token) (action_new_token form._rr.rrid)
|
||||
|
||||
foot_content :: AcceptedRRTypes -> Array (HH.HTML w i)
|
||||
foot_content x =
|
||||
case rr_modal of
|
||||
NewRRModal _ -> [Web.btn_add (action_validate_rr x)]
|
||||
UpdateRRModal -> [Web.btn_save action_validate_local_rr ] <> case x of
|
||||
A -> [newtokenbtn]
|
||||
AAAA -> [newtokenbtn]
|
||||
_ -> []
|
||||
_ -> [Web.p "rr_modal should either be NewRRModal or UpdateRRModal."]
|
||||
|
||||
template :: Array (HH.HTML w i) -> Array (HH.HTML w i) -> HH.HTML w i
|
||||
template content foot_ = Web.modal title content foot
|
||||
where
|
||||
title = case rr_modal of
|
||||
NoModal -> "Error: no modal should be displayed"
|
||||
NewRRModal t_ -> "New " <> show t_ <> " resource record"
|
||||
UpdateRRModal -> "Update " <> form._rr.rrtype <> " Resource Record"
|
||||
RemoveRRModal rr_id -> "Error: should display removal modal instead (for resource record " <> show rr_id <> ")"
|
||||
foot = foot_ <> [Web.cancel_button action_cancel_modal]
|
|
@ -1,3 +1,6 @@
|
|||
-- | `App.Templates.Table` gathers all the website's tables, providing
|
||||
-- | an easy way to duplicate tables in different pages and to display
|
||||
-- | content in a consistent manner.
|
||||
module App.Templates.Table
|
||||
( owned_domains
|
||||
, shared_domains
|
||||
|
@ -17,8 +20,8 @@ import Data.Maybe (Maybe(..), fromMaybe, maybe)
|
|||
|
||||
import Data.Tuple (Tuple(..))
|
||||
|
||||
import Style as Style
|
||||
import Style.Button as Button
|
||||
import Web as Web
|
||||
import Web.Button as Button
|
||||
import Bulma as Bulma
|
||||
import Halogen.HTML as HH
|
||||
import Halogen.HTML.Properties as HP
|
||||
|
@ -122,15 +125,15 @@ resource_records records action_create_or_update_rr action_delete_rr action_new_
|
|||
all_dkim_rr = all_XX_rr "DKIM"
|
||||
all_dmarc_rr = all_XX_rr "DMARC"
|
||||
|
||||
tag_soa = Style.tags [Style.tag_ro "SOA", Style.tag_ro "read only"]
|
||||
tag_basic = Style.tags [Style.tag "Basic Resource Records (A, AAAA, PTR, NS, TXT)"]
|
||||
tag_mx = Style.tags [Style.tag "MX"]
|
||||
tag_caa = Style.tags [Style.tag "CAA"]
|
||||
tag_srv = Style.tags [Style.tag "SRV"]
|
||||
tag_spf = Style.tags [Style.tag "SPF"]
|
||||
tag_dkim = Style.tags [Style.tag "DKIM"]
|
||||
tag_dmarc = Style.tags [Style.tag "DMARC"]
|
||||
tag_basic_ro = Style.tags [Style.tag_ro "Basic Resource Records", Style.tag_ro "read only"]
|
||||
tag_soa = Web.tags [Web.tag_ro "SOA", Web.tag_ro "read only"]
|
||||
tag_basic = Web.tags [Web.tag "Basic Resource Records (A, AAAA, PTR, NS, TXT)"]
|
||||
tag_mx = Web.tags [Web.tag "MX"]
|
||||
tag_caa = Web.tags [Web.tag "CAA"]
|
||||
tag_srv = Web.tags [Web.tag "SRV"]
|
||||
tag_spf = Web.tags [Web.tag "SPF"]
|
||||
tag_dkim = Web.tags [Web.tag "DKIM"]
|
||||
tag_dmarc = Web.tags [Web.tag "DMARC"]
|
||||
tag_basic_ro = Web.tags [Web.tag_ro "Basic Resource Records", Web.tag_ro "read only"]
|
||||
|
||||
rr_box :: Array HH.ClassName -- css classes (such as colors)
|
||||
-> HH.HTML w i -- box title (type of data)
|
||||
|
@ -509,15 +512,15 @@ port_header = HH.abbr
|
|||
[ HH.text "Port" ]
|
||||
|
||||
display_mechanisms :: forall w i. (Int -> i) -> Array RR.Mechanism -> HH.HTML w i
|
||||
display_mechanisms _ [] = Style.p "You don't have any mechanism."
|
||||
display_mechanisms _ [] = Web.p "You don't have any mechanism."
|
||||
display_mechanisms action_remove_mechanism ms =
|
||||
Style.table [] [ mechanism_table_header, HH.tbody_ $ map render_mechanism_row $ attach_id 0 ms]
|
||||
Web.table [] [ mechanism_table_header, HH.tbody_ $ map render_mechanism_row $ attach_id 0 ms]
|
||||
where
|
||||
render_mechanism_row :: (Tuple Int RR.Mechanism) -> HH.HTML w i
|
||||
render_mechanism_row (Tuple i m) = HH.tr_
|
||||
[ txt_name $ maybe "" show_qualifier m.q
|
||||
, HH.td_ [ Style.p $ show_mechanism_type m.t ]
|
||||
, HH.td_ [ Style.p m.v ]
|
||||
, HH.td_ [ Web.p $ show_mechanism_type m.t ]
|
||||
, HH.td_ [ Web.p m.v ]
|
||||
, HH.td_ [ Button.alert_btn "x" (action_remove_mechanism i) ]
|
||||
]
|
||||
mechanism_table_header :: HH.HTML w i
|
||||
|
@ -530,14 +533,14 @@ display_mechanisms action_remove_mechanism ms =
|
|||
]
|
||||
|
||||
display_modifiers :: forall w i. (Int -> i) -> Array RR.Modifier -> HH.HTML w i
|
||||
display_modifiers _ [] = Style.p "You don't have any modifier."
|
||||
display_modifiers _ [] = Web.p "You don't have any modifier."
|
||||
display_modifiers action_remove_modifier ms =
|
||||
Style.table [] [ modifier_table_header, HH.tbody_ $ map render_modifier_row $ attach_id 0 ms]
|
||||
Web.table [] [ modifier_table_header, HH.tbody_ $ map render_modifier_row $ attach_id 0 ms]
|
||||
where
|
||||
render_modifier_row :: (Tuple Int RR.Modifier) -> HH.HTML w i
|
||||
render_modifier_row (Tuple i m) = HH.tr_
|
||||
[ HH.td_ [ Style.p $ show_modifier_type m.t ]
|
||||
, HH.td_ [ Style.p m.v ]
|
||||
[ HH.td_ [ Web.p $ show_modifier_type m.t ]
|
||||
, HH.td_ [ Web.p m.v ]
|
||||
, HH.td_ [ Button.alert_btn "x" (action_remove_modifier i) ]
|
||||
]
|
||||
modifier_table_header :: HH.HTML w i
|
||||
|
@ -550,12 +553,12 @@ display_modifiers action_remove_modifier ms =
|
|||
|
||||
display_dmarc_mail_addresses :: forall w i. (Int -> i) -> Array DMARC.DMARCURI -> HH.HTML w i
|
||||
display_dmarc_mail_addresses f ms =
|
||||
Style.table [] [ dmarc_dmarcuri_table_header, HH.tbody_ $ map render_dmarcuri_row $ attach_id 0 ms]
|
||||
Web.table [] [ 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 i
|
||||
render_dmarcuri_row (Tuple i m) = HH.tr_
|
||||
[ HH.td_ [ Style.p m.mail ]
|
||||
, HH.td_ [ Style.p $ maybe "(no size limit)" show m.limit ]
|
||||
[ HH.td_ [ Web.p m.mail ]
|
||||
, HH.td_ [ Web.p $ maybe "(no size limit)" show m.limit ]
|
||||
, HH.td_ [ Button.alert_btn "x" (f i) ]
|
||||
]
|
||||
dmarc_dmarcuri_table_header :: HH.HTML w i
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
module App.Text.Explanations where
|
||||
import Halogen.HTML as HH
|
||||
import Halogen.HTML.Properties as HP
|
||||
import Style as Style
|
||||
import Web as Web
|
||||
import CSSClasses as C
|
||||
|
||||
expl' :: forall w i. String -> HH.HTML w i
|
||||
expl' text = expl [Style.p text]
|
||||
expl' text = expl [Web.p text]
|
||||
expl :: forall w i. Array (HH.HTML w i) -> HH.HTML w i
|
||||
expl content = Style.div_content [] [ Style.explanation content ]
|
||||
expl content = Web.div_content [] [ Web.explanation content ]
|
||||
expl_txt :: forall w i. String -> HH.HTML w i
|
||||
expl_txt content = Style.explanation [ Style.p content ]
|
||||
expl_txt content = Web.explanation [ Web.p content ]
|
||||
|
||||
col :: forall w i. Array (HH.HTML w i) -> HH.HTML w i
|
||||
col arr = Style.column_ [ Style.box arr ]
|
||||
col arr = Web.column_ [ Web.box arr ]
|
||||
|
||||
tokens :: forall w i. HH.HTML w i
|
||||
tokens = HH.div_
|
||||
[ Style.h3 "What are tokens?"
|
||||
[ Web.h3 "What are tokens?"
|
||||
, expl' """
|
||||
Tokens are a simple way to update a resource record (A or AAAA) with your current IP address.
|
||||
"""
|
||||
|
@ -30,61 +30,61 @@ tokens = HH.div_
|
|||
, HH.u_ [HH.text "<your-token>"]
|
||||
]
|
||||
]
|
||||
, Style.p "For example: https://www.netlib.re/token-update/53be0c45-61c4-4d29-8ae9-c2cc8767603d"
|
||||
, Style.hr
|
||||
, Style.h3 "How to automate the update of my IP address?"
|
||||
, Style.p "On Linux, you can make your computer access the update link with the following command."
|
||||
, expl [ Style.strong "wget https://www.netlib.re/token-update/<your-token>" ]
|
||||
, Style.p """
|
||||
, Web.p "For example: https://www.netlib.re/token-update/53be0c45-61c4-4d29-8ae9-c2cc8767603d"
|
||||
, Web.hr
|
||||
, Web.h3 "How to automate the update of my IP address?"
|
||||
, Web.p "On Linux, you can make your computer access the update link with the following command."
|
||||
, expl [ Web.strong "wget https://www.netlib.re/token-update/<your-token>" ]
|
||||
, Web.p """
|
||||
No need for a more complex program. This works just fine.
|
||||
And you can run this command every hour.
|
||||
For example, in your crontab (Linux and Unix related):
|
||||
"""
|
||||
, expl [ Style.strong "0 * * * * wget <url>" ]
|
||||
, Style.p """
|
||||
, expl [ Web.strong "0 * * * * wget <url>" ]
|
||||
, Web.p """
|
||||
Commands for other operating systems may differ, but you get the idea.
|
||||
"""
|
||||
, Style.hr
|
||||
, Style.h3 "The obvious trap ⚠"
|
||||
, Style.p """
|
||||
, Web.hr
|
||||
, Web.h3 "The obvious trap ⚠"
|
||||
, Web.p """
|
||||
Make sure to access the website using the related IP address.
|
||||
To update an IPv6 address (AAAA), force your application to access the URL using an IPv6 address.
|
||||
"""
|
||||
, expl [ HH.p_ [ Style.strong "wget -6 <url>" ]
|
||||
, expl [ HH.p_ [ Web.strong "wget -6 <url>" ]
|
||||
, HH.p_ [ HH.text "To force the use of an IPv6 address." ]
|
||||
, HH.p_ [ Style.strong "wget -4 <url>" ]
|
||||
, HH.p_ [ Web.strong "wget -4 <url>" ]
|
||||
, HH.p_ [ HH.text "To force the use of an IPv4 address." ]
|
||||
]
|
||||
]
|
||||
|
||||
basics :: forall w i. HH.HTML w i
|
||||
basics = HH.div_
|
||||
[ Style.h3 "Basics of DNS"
|
||||
, Style.p """
|
||||
[ Web.h3 "Basics of DNS"
|
||||
, Web.p """
|
||||
The domain name system (DNS) enables people share a name instead of an address to find a website or service.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
To configure a zone, the first steps are trivial.
|
||||
"""
|
||||
|
||||
, Style.hr
|
||||
, Style.h3 "I have something to host (A and AAAA records)."
|
||||
, Web.hr
|
||||
, Web.h3 "I have something to host (A and AAAA records)."
|
||||
, expl' "Let's assume you have a web server and you host your website somewhere."
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
You want an A (IPv4) or AAAA (IPv6) record pointing to your server, named "enigma" for example.
|
||||
"""
|
||||
|
||||
, Style.hr
|
||||
, Style.h3 "You need other names pointing to your server (CNAME records)."
|
||||
, Style.p """
|
||||
, Web.hr
|
||||
, Web.h3 "You need other names pointing to your server (CNAME records)."
|
||||
, Web.p """
|
||||
You may not want to use the name of your server "enigma" directly.
|
||||
Instead, you may want the usual names for your services, such as "www" or "blog".
|
||||
CNAME records are basically aliases, exactly to that end.
|
||||
"""
|
||||
|
||||
, Style.hr
|
||||
, Style.h3 "If you have other servers, just add more A or AAAA records."
|
||||
, Style.p """
|
||||
, Web.hr
|
||||
, Web.h3 "If you have other servers, just add more A or AAAA records."
|
||||
, Web.p """
|
||||
Tip: choose relevant names for your servers then add CNAME records.
|
||||
For example, you can have an A record named "server1" and a CNAME "www" pointing to it.
|
||||
The service isn't pointing to an actual IP address directly,
|
||||
|
@ -92,24 +92,24 @@ basics = HH.div_
|
|||
You don't need to remember the IP address of each of your servers.
|
||||
"""
|
||||
|
||||
, Style.hr
|
||||
, Style.h3 "I want an email server."
|
||||
, Web.hr
|
||||
, Web.h3 "I want an email server."
|
||||
, expl' """
|
||||
Hosting a mail server is quite complex.
|
||||
This section will focus on the main parts regarding the DNS.
|
||||
"""
|
||||
, Style.notification_danger' """
|
||||
, Web.notification_danger' """
|
||||
The actual configuration of your mail server is complex and depends on your choice of software.
|
||||
This won't be covered here.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
You need an MX record pointing to your "www" A (or AAAA) record.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
Having an MX record isn't enough to handle a mail server.
|
||||
You need to use a few spam mitigation mechanisms.
|
||||
"""
|
||||
, Style.columns_
|
||||
, Web.columns_
|
||||
[ col
|
||||
[ expl' """
|
||||
Spam mitigation 1: tell what are the right mail servers for your domain with Sender Policy Framework (SPF).
|
||||
|
@ -138,27 +138,27 @@ basics = HH.div_
|
|||
, expl_txt """
|
||||
Last but not least, DMARC.
|
||||
"""
|
||||
, Style.hr
|
||||
, Style.p """
|
||||
, Web.hr
|
||||
, Web.p """
|
||||
DMARC enables to check the "From:" field of a mail, based on the SPF and DKIM mechanisms.
|
||||
Thus, domains with a DMARC record enable to only allow verified mails.
|
||||
Valid emails come from an authorized IP address (SPF), are signed by the verified email server (DKIM) and have an email address coming from a verified domain (DMARC) related to the two previous spam mitigation mechanisms.
|
||||
"""
|
||||
, Style.hr
|
||||
, Style.p """
|
||||
, Web.hr
|
||||
, Web.p """
|
||||
With DMARC, you won't accept an email from "hacker@example.com" because it was sent by another domain with a valid SPF and DKIM.
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
, Style.hr
|
||||
, Style.h3 "How to automate the update of my IP address?"
|
||||
, Style.p "Check out the \"Tokens? 🤨\" tab."
|
||||
, Web.hr
|
||||
, Web.h3 "How to automate the update of my IP address?"
|
||||
, Web.p "Check out the \"Tokens? 🤨\" tab."
|
||||
]
|
||||
|
||||
a_introduction :: forall w i. Array (HH.HTML w i)
|
||||
a_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The A record enables to bind an IPv4 address to a domain.
|
||||
"""
|
||||
, HH.p []
|
||||
|
@ -175,7 +175,7 @@ a_introduction =
|
|||
|
||||
aaaa_introduction :: forall w i. Array (HH.HTML w i)
|
||||
aaaa_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The AAAA record enables to bind an IPv6 address to a domain.
|
||||
"""
|
||||
, HH.p []
|
||||
|
@ -192,7 +192,7 @@ aaaa_introduction =
|
|||
|
||||
cname_introduction :: forall w i. Array (HH.HTML w i)
|
||||
cname_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The CNAME record enables to provide alternative names to records.
|
||||
"""
|
||||
, HH.p []
|
||||
|
@ -209,7 +209,7 @@ cname_introduction =
|
|||
|
||||
mx_introduction :: forall w i. Array (HH.HTML w i)
|
||||
mx_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The MX record enables to add a mail server to your zone.
|
||||
"""
|
||||
, HH.p []
|
||||
|
@ -221,20 +221,20 @@ mx_introduction =
|
|||
This page talks about the DNS aspect of it, but doesn't cover all you need to know to actually host a mail server, by a long shot.
|
||||
"""
|
||||
]
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
Anyway, the MX record itself is simple to understand.
|
||||
Let's say you have a server named "server1" with your mail service.
|
||||
The MX record can be named "mail" and it will target "server1".
|
||||
Of course, "server1" needs a record for its IP address (A or AAAA).
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
The priority field is important only in case you have multiple mail servers; keep the default value.
|
||||
"""
|
||||
]
|
||||
|
||||
txt_introduction :: forall w i. Array (HH.HTML w i)
|
||||
txt_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The TXT record enables to declare a small text.
|
||||
"""
|
||||
, HH.p []
|
||||
|
@ -246,7 +246,7 @@ txt_introduction =
|
|||
TXT records are used in several places, for example for mail security through SPF, DKIM and DMARC records.
|
||||
"""
|
||||
]
|
||||
, Style.notification_danger' """
|
||||
, Web.notification_danger' """
|
||||
All of these specific records have a dedicated user interface on this website;
|
||||
use them instead of writing these records by yourself.
|
||||
"""
|
||||
|
@ -254,19 +254,19 @@ txt_introduction =
|
|||
|
||||
ns_introduction :: forall w i. Array (HH.HTML w i)
|
||||
ns_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The NS record enables to declare a new Name Server, meaning a new server that would serve this zone.
|
||||
"""
|
||||
, Style.notification_danger' "🚨 Advice for beginners: do not use this resource record."
|
||||
, Web.notification_danger' "🚨 Advice for beginners: do not use this resource record."
|
||||
]
|
||||
|
||||
caa_introduction :: forall w i. Array (HH.HTML w i)
|
||||
caa_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
The CAA record enables to specify a certification authority that is authorized to issue certificates for the domain.
|
||||
The idea is to reduce the risk of unintended certificate mis-issue.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
Certification authorities (CA) may issue certificates for any domain.
|
||||
Thus, any CA may provide certificates for a domain (let's say google.com) to any hacker that can now impersonate the domain.
|
||||
The CAA record allows to say what is the authorized CA for the domain, preventing this kind of attacks.
|
||||
|
@ -282,7 +282,7 @@ caa_introduction =
|
|||
|
||||
dkim_introduction :: forall w i. Array (HH.HTML w i)
|
||||
dkim_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
DKIM is a way to share a public signature key for the domain.
|
||||
This enables emails to be signed by the sender and for the receiver to verify the origin of the mail.
|
||||
"""
|
||||
|
@ -293,14 +293,14 @@ dkim_introduction =
|
|||
"""
|
||||
, HH.u_ [HH.text "selector"]
|
||||
, HH.text " is "
|
||||
, Style.strong "default"
|
||||
, Web.strong "default"
|
||||
, HH.text "."
|
||||
]
|
||||
]
|
||||
|
||||
dmarc_introduction :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_introduction =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
DMARC is a spam mitigation mechanism on top of SPF and DKIM.
|
||||
Upon receiving a mail, the server checks whether the "From:" field of the mail is consistent with the SPF and DKIM
|
||||
records of the sender's domain.
|
||||
|
@ -311,11 +311,11 @@ dmarc_introduction =
|
|||
|
||||
dmarc_policy :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_policy =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
DMARC record enables to tell receivers what to do with a non-conforming message,
|
||||
i.e. a message that wasn't properly secured with SPF and DKIM.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
This message can either be accepted ("None") or rejected, or even quarantined, meaning to be considered as suspicious.
|
||||
This can take different forms, such as being flagged, marked as spam or have a high "spam score", it's up to the receiver.
|
||||
"""
|
||||
|
@ -323,34 +323,34 @@ dmarc_policy =
|
|||
|
||||
dmarc_sp_policy :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_sp_policy =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Same as the previous entry, but for sub-domains.
|
||||
"""
|
||||
]
|
||||
|
||||
dmarc_adkim :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_adkim =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Consistency policy for DKIM. Tell what should be considered acceptable.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
This is about the relation between the email "From:" field and the domain field of the DKIM signature ("d:").
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
The policy can be either strict (both should be identical) or relaxed (both in the same Organizational Domain).
|
||||
"""
|
||||
]
|
||||
|
||||
dmarc_aspf :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_aspf =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Consistency policy for SPF. Tell what should be considered acceptable.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
First, SPF should produce a passing result.
|
||||
Then, the "From:" and the "MailFrom:" fields of the received email are checked.
|
||||
"""
|
||||
, Style.p """
|
||||
, Web.p """
|
||||
In strict mode, both fields should be identical.
|
||||
In relaxed mode, they can be different, but in the same Organizational Domain.
|
||||
"""
|
||||
|
@ -367,28 +367,28 @@ dmarc_aspf =
|
|||
]
|
||||
, HH.p_
|
||||
[ HH.text "See "
|
||||
, Style.outside_link [] "https://publicsuffix.org/" "publicsuffix.org"
|
||||
, Web.outside_link [] "https://publicsuffix.org/" "publicsuffix.org"
|
||||
, HH.text " for a list of Organizational Domains."
|
||||
]
|
||||
]
|
||||
|
||||
dmarc_contact :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_contact =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
In case you want to receive error reports, enter email addresses that should receive either an aggregated report or a detailed report of the occurring errors.
|
||||
"""
|
||||
]
|
||||
|
||||
dmarc_ri :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_ri =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Requested report interval. Default is 86400.
|
||||
"""
|
||||
]
|
||||
|
||||
dmarc_pct :: forall w i. Array (HH.HTML w i)
|
||||
dmarc_pct =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Sampling rate.
|
||||
Percentage of messages subjected to the requested policy.
|
||||
"""
|
||||
|
@ -397,7 +397,7 @@ dmarc_pct =
|
|||
|
||||
dkim_default_algorithms :: forall w i. Array (HH.HTML w i)
|
||||
dkim_default_algorithms =
|
||||
[ Style.p """
|
||||
[ Web.p """
|
||||
Default values should be fine (RSA + SHA256), change them only if you know what you are doing.
|
||||
Just enter your public key.
|
||||
"""
|
||||
|
@ -435,7 +435,7 @@ spf_introduction =
|
|||
|
||||
spf_default_behavior :: forall w i. Array (HH.HTML w i)
|
||||
spf_default_behavior = [
|
||||
Style.p """
|
||||
Web.p """
|
||||
What should someone do when receiving a mail from your email address but not from a listed domain or IP address?
|
||||
"""
|
||||
, HH.p_ [ HH.text """
|
||||
|
@ -464,7 +464,7 @@ spf_default_behavior = [
|
|||
|
||||
srv_introduction :: forall w i. Array (HH.HTML w i)
|
||||
srv_introduction =
|
||||
[ Style.p "The SRV record is a DNS resource record for specifying the location of services."
|
||||
[ Web.p "The SRV record is a DNS resource record for specifying the location of services."
|
||||
, HH.p_ [ HH.text "Given a specific "
|
||||
, HH.u_ [HH.text "service name"]
|
||||
, HH.text " (which may be arbitrary) and a "
|
||||
|
@ -487,38 +487,38 @@ website_abuse_address = "abuse AT netlib.re" :: String
|
|||
|
||||
legal_notice :: forall w i. HH.HTML w i
|
||||
legal_notice = HH.div_
|
||||
[ Style.h3 "Legal Notice"
|
||||
[ Web.h3 "Legal Notice"
|
||||
|
||||
, Style.strong "Website Publisher"
|
||||
, Web.strong "Website Publisher"
|
||||
, expl [ HH.p_ [ HH.text "You can contact this website's owner and publisher at: "
|
||||
, Style.strong website_owner_address
|
||||
, Web.strong website_owner_address
|
||||
]
|
||||
, HH.p_ [ HH.text "For legal matter: "
|
||||
, Style.strong website_abuse_address
|
||||
, Web.strong website_abuse_address
|
||||
]
|
||||
]
|
||||
|
||||
, Style.strong "Website Hosting"
|
||||
, Web.strong "Website Hosting"
|
||||
, expl [ HH.p_ [ HH.text "This website is hosted by "
|
||||
, Style.strong "Alsace Réseau Neutre"
|
||||
, Web.strong "Alsace Réseau Neutre"
|
||||
, HH.text "."
|
||||
, HH.br_
|
||||
, HH.text "Website: "
|
||||
, Style.outside_link [] "https://arn-fai.net" "arn-fai.net"
|
||||
, Web.outside_link [] "https://arn-fai.net" "arn-fai.net"
|
||||
, HH.br_
|
||||
, HH.text "Address & contact: "
|
||||
, Style.outside_link [] "https://arn-fai.net/fr/mentions" "Legal Notice of ARN"
|
||||
, Web.outside_link [] "https://arn-fai.net/fr/mentions" "Legal Notice of ARN"
|
||||
]
|
||||
]
|
||||
|
||||
, Style.strong "Intellectual Property"
|
||||
, Web.strong "Intellectual Property"
|
||||
, expl' """
|
||||
The code of this website is released under the ISC License. You
|
||||
are free to copy, modify, and distribute the code, provided
|
||||
that you comply with the terms of the ISC License.
|
||||
"""
|
||||
|
||||
, Style.strong "Personal Data Collection"
|
||||
, Web.strong "Personal Data Collection"
|
||||
, expl' """
|
||||
This website collects only the personal data necessary for its proper functioning.
|
||||
This includes data such as: a login (arbitrary set of
|
||||
|
@ -526,12 +526,12 @@ legal_notice = HH.div_
|
|||
to contact the owner of the domain, domain names and zone data.
|
||||
"""
|
||||
|
||||
, Style.strong "Data Sharing"
|
||||
, Web.strong "Data Sharing"
|
||||
, expl' """
|
||||
None of the collected data will be shared to third parties.
|
||||
"""
|
||||
|
||||
, Style.strong "Data Retention"
|
||||
, Web.strong "Data Retention"
|
||||
, expl' """
|
||||
The personal data collected on this website will be retained
|
||||
for as long as necessary to fulfill the purposes for which it
|
||||
|
@ -549,16 +549,16 @@ legal_notice = HH.div_
|
|||
After this period, all data will be securely deleted.
|
||||
"""
|
||||
|
||||
, Style.strong "Liability"
|
||||
, Web.strong "Liability"
|
||||
, expl
|
||||
[ Style.p
|
||||
[ Web.p
|
||||
"""
|
||||
The publisher of this website makes every effort to ensure that
|
||||
the website functions properly and that all data is protected
|
||||
to the best of their ability.
|
||||
"""
|
||||
|
||||
, Style.p
|
||||
, Web.p
|
||||
"""
|
||||
However, despite all reasonable precautions, the publisher
|
||||
cannot guarantee that the website will always be free of errors,
|
||||
|
@ -576,7 +576,7 @@ legal_notice = HH.div_
|
|||
]
|
||||
]
|
||||
|
||||
, Style.p
|
||||
, Web.p
|
||||
"""
|
||||
By using this website, users acknowledge that they accept the
|
||||
inherent risks associated with the use of online services. The
|
||||
|
@ -585,13 +585,13 @@ legal_notice = HH.div_
|
|||
"""
|
||||
]
|
||||
|
||||
, Style.strong "GDPR compliance"
|
||||
, Web.strong "GDPR compliance"
|
||||
, expl [ HH.p_ [ HH.text """
|
||||
You have the right to access, correct and delete your personal
|
||||
data at any time via this website or by contacting us at the
|
||||
following email address:
|
||||
"""
|
||||
, Style.strong website_owner_address
|
||||
, Web.strong website_owner_address
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
18
src/App/Type/Field.purs
Normal file
18
src/App/Type/Field.purs
Normal file
|
@ -0,0 +1,18 @@
|
|||
module App.Type.Field where
|
||||
|
||||
import App.Type.ResourceRecord as RR
|
||||
|
||||
data Field
|
||||
= Domain String
|
||||
| TTL String
|
||||
| Target String
|
||||
| Priority String
|
||||
| Weight String
|
||||
| Port String
|
||||
| SPF_v String
|
||||
| SPF_mechanisms (Array RR.Mechanism)
|
||||
| SPF_modifiers (Array RR.Modifier)
|
||||
| SPF_q RR.Qualifier
|
||||
|
||||
| CAA_flag String
|
||||
| CAA_value String
|
149
src/App/Type/RRForm.purs
Normal file
149
src/App/Type/RRForm.purs
Normal file
|
@ -0,0 +1,149 @@
|
|||
-- | `App.Type.RRForm` provides types used to manage the modification
|
||||
-- | of resource records.
|
||||
-- | FIXME: this state is messy AF and should be replaced.
|
||||
module App.Type.RRForm where
|
||||
|
||||
import Prelude
|
||||
import App.Type.AcceptedRRTypes (AcceptedRRTypes(..))
|
||||
import App.Type.ResourceRecord
|
||||
import App.Type.DKIM as DKIM
|
||||
import App.Type.DMARC as DMARC
|
||||
import Data.Maybe
|
||||
import App.Type.ResourceRecord as RR
|
||||
import App.Type.CAA as CAA
|
||||
import App.Validation.Email as Email
|
||||
import App.Validation.DNS as Validation
|
||||
import App.Type.RRId
|
||||
|
||||
-- | TMP: temporary stored values regarding specific records such as SPF,
|
||||
-- | DKIM and DMARC.
|
||||
type TMP =
|
||||
{
|
||||
-- SPF details.
|
||||
spf :: { mechanism_q :: String
|
||||
, mechanism_t :: String
|
||||
, mechanism_v :: String
|
||||
, modifier_t :: String
|
||||
, modifier_v :: String
|
||||
}
|
||||
|
||||
-- DMARC details.
|
||||
, dmarc_mail :: String
|
||||
, dmarc_mail_limit :: Maybe Int
|
||||
, dmarc :: DMARC.DMARC
|
||||
|
||||
-- DKIM details.
|
||||
, dkim :: DKIM.DKIM
|
||||
}
|
||||
|
||||
-- | `RRForm` is the necessary state to modify a resource record.
|
||||
-- | It contains the currently manipulated record, detected errors, along with some temporary values.
|
||||
type RRForm =
|
||||
{ _rr :: ResourceRecord
|
||||
, _errors :: Array Validation.Error
|
||||
, _dmarc_mail_errors :: Array Email.Error
|
||||
, _zonefile :: Maybe String
|
||||
, tmp :: TMP
|
||||
}
|
||||
|
||||
default_empty_rr :: ResourceRecord
|
||||
default_empty_rr = default_rr A ""
|
||||
|
||||
default_qualifier_str = "hard_fail" :: String
|
||||
default_caa = { flag: 0, tag: CAA.Issue, value: "letsencrypt.org" } :: CAA.CAA
|
||||
|
||||
default_rr :: AcceptedRRTypes -> String -> ResourceRecord
|
||||
default_rr t domain =
|
||||
case t of
|
||||
A -> emptyRR { rrtype = "A", name = "server1", target = "192.0.2.1" }
|
||||
AAAA -> emptyRR { rrtype = "AAAA", name = "server1", target = "2001:db8::1" }
|
||||
TXT -> emptyRR { rrtype = "TXT", name = "txt", target = "some text" }
|
||||
CNAME -> emptyRR { rrtype = "CNAME", name = "www", target = "server1" }
|
||||
NS -> emptyRR { rrtype = "NS", name = (domain <> "."), target = "ns0.example.com." }
|
||||
MX -> emptyRR { rrtype = "MX", name = "mail", target = "server1", priority = Just 10 }
|
||||
CAA -> emptyRR { rrtype = "CAA", name = "", target = "", caa = Just default_caa }
|
||||
SRV -> emptyRR { rrtype = "SRV", name = "voip", target = "server1"
|
||||
, port = Just 5061, weight = Just 100, priority = Just 10, protocol = Just RR.TCP }
|
||||
SPF -> emptyRR { rrtype = "SPF", name = "", target = ""
|
||||
, mechanisms = Just default_mechanisms, q = Just RR.HardFail }
|
||||
DKIM -> emptyRR { rrtype = "DKIM", name = "default._domainkey", target = "" }
|
||||
DMARC -> emptyRR { rrtype = "DMARC", name = "_dmarc", target = "" }
|
||||
where
|
||||
default_mechanisms = maybe [] (\x -> [x]) $ to_mechanism "pass" "mx" ""
|
||||
|
||||
mkEmptyRRForm :: RRForm
|
||||
mkEmptyRRForm =
|
||||
{
|
||||
-- This is the state for the new RR modal.
|
||||
_rr: default_empty_rr
|
||||
-- List of errors within the form in new RR modal.
|
||||
, _errors: []
|
||||
, _dmarc_mail_errors: []
|
||||
, _zonefile: Nothing
|
||||
, tmp: { spf: { mechanism_q: "pass"
|
||||
, mechanism_t: "a"
|
||||
, mechanism_v: ""
|
||||
, modifier_t: "redirect"
|
||||
, modifier_v: ""
|
||||
}
|
||||
, dkim: DKIM.emptyDKIMRR
|
||||
, dmarc: DMARC.emptyDMARCRR
|
||||
, dmarc_mail: ""
|
||||
, dmarc_mail_limit: Nothing
|
||||
}
|
||||
}
|
||||
|
||||
data RRUpdateValue
|
||||
-- | Ask a (new) token for a RR.
|
||||
= NewToken RRId
|
||||
| CAA_tag Int
|
||||
| SRV_Protocol Int
|
||||
| SPF_Mechanism_q Int
|
||||
| SPF_Mechanism_t Int
|
||||
| SPF_Mechanism_v String
|
||||
| SPF_Modifier_t Int
|
||||
| SPF_Modifier_v String
|
||||
| SPF_Qualifier Int
|
||||
|
||||
-- | Remove a SPF mechanism of the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_remove_mechanism Int
|
||||
-- | Remove a SPF modifier of the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_remove_modifier Int
|
||||
|
||||
-- | Add a SPF mechanism to the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_Mechanism_Add
|
||||
-- | Add a SPF modifier to the currently modified (SPF) entry (see `_currentRR`).
|
||||
| SPF_Modifier_Add
|
||||
|
||||
-- | Change the temporary mail address for DMARC.
|
||||
| DMARC_mail String
|
||||
|
||||
-- | Change the temporary report size limit for DMARC.
|
||||
| DMARC_mail_limit String
|
||||
|
||||
-- | Change the requested report interval.
|
||||
| DMARC_ri String
|
||||
|
||||
-- | Add a new mail address to the DMARC rua list.
|
||||
| DMARC_rua_Add
|
||||
|
||||
-- | Add a new mail address to the DMARC ruf list.
|
||||
| DMARC_ruf_Add
|
||||
|
||||
-- | Remove a mail address of the DMARC rua list.
|
||||
| DMARC_remove_rua Int
|
||||
|
||||
-- | Remove a mail address of the DMARC ruf list.
|
||||
| DMARC_remove_ruf Int
|
||||
|
||||
| DMARC_policy Int
|
||||
| DMARC_sp_policy Int
|
||||
| DMARC_adkim Int
|
||||
| DMARC_aspf Int
|
||||
| DMARC_pct String
|
||||
| DMARC_fo Int
|
||||
|
||||
| DKIM_hash_algo Int
|
||||
| DKIM_sign_algo Int
|
||||
| DKIM_pubkey String
|
||||
| DKIM_note String
|
3
src/App/Type/RRId.purs
Normal file
3
src/App/Type/RRId.purs
Normal file
|
@ -0,0 +1,3 @@
|
|||
module App.Type.RRId where
|
||||
|
||||
type RRId = Int
|
16
src/App/Type/RRModal.purs
Normal file
16
src/App/Type/RRModal.purs
Normal file
|
@ -0,0 +1,16 @@
|
|||
-- | `App.Type.RRModal.RRModal` provides the states of a modal related
|
||||
-- | to resource management: no modal because no RR is currently
|
||||
-- | selected, new RR because a new resource will be requested, update
|
||||
-- | and finally removal, to ask for confirmation.
|
||||
-- |
|
||||
-- | FIXME: TODO: WIP: should this be replaced by something like `CRUD`?
|
||||
module App.Type.RRModal where
|
||||
|
||||
import App.Type.RRId
|
||||
import App.Type.AcceptedRRTypes (AcceptedRRTypes)
|
||||
|
||||
data RRModal
|
||||
= NoModal
|
||||
| NewRRModal AcceptedRRTypes
|
||||
| UpdateRRModal
|
||||
| RemoveRRModal RRId
|
|
@ -9,7 +9,6 @@ import DOM.HTML.Indexed as DHI
|
|||
import Halogen.HTML.Properties as HP
|
||||
import Halogen.HTML.Events as HE
|
||||
|
||||
import DOM.HTML.Indexed.AutocompleteType (AutocompleteType(..))
|
||||
import CSSClasses as C
|
||||
|
||||
import Halogen.HTML.Core (AttrName(..))
|
||||
|
|
14
src/Web.purs
Normal file
14
src/Web.purs
Normal file
|
@ -0,0 +1,14 @@
|
|||
-- | `Web` module is an abstraction over most HTML-related code in order
|
||||
-- | to have a consistent style through all the website.
|
||||
module Web
|
||||
( module Bulma
|
||||
, module Web.Button
|
||||
, module Web.Input
|
||||
, module Web.Tag
|
||||
) where
|
||||
|
||||
import Web.Button
|
||||
import Web.Input
|
||||
import Web.Tag
|
||||
|
||||
import Bulma
|
|
@ -1,4 +1,4 @@
|
|||
module Style.Button
|
||||
module Web.Button
|
||||
( alert_btn
|
||||
, alert_btn_abbr
|
||||
, btn
|
|
@ -1,4 +1,4 @@
|
|||
module Style.Input
|
||||
module Web.Input
|
||||
( email_input
|
||||
, password_input
|
||||
, password_input_confirmation
|
|
@ -1,16 +1,4 @@
|
|||
module Style
|
||||
( module Bulma
|
||||
, module Style.Button
|
||||
, module Style.Input
|
||||
, tags
|
||||
, tag
|
||||
, tag_ro
|
||||
) where
|
||||
|
||||
import Style.Button
|
||||
import Style.Input
|
||||
|
||||
import Bulma
|
||||
module Web.Tag where
|
||||
|
||||
import Halogen.HTML as HH
|
||||
import Halogen.HTML.Properties as HP
|
Loading…
Add table
Reference in a new issue