mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-04-01 18:16:24 +00:00
* SMP server information * fix tests * country codes * smp-server: serve contact and link pages from static files (#1084) * smp-server: serve contact and link pages from static files * generate index * use params from ini * render using ServerInformation * tweak templates * update * fix some html * smp-server: fix layout (#1097) * smp-server: fix layout * port fixes to link page --------- Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com> * update server information page --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> Co-authored-by: M. Sarmad Qadeer <MSarmadQadeer@gmail.com> * update server info * web: improve server info page design (#1166) * web: improve server info page design * web: fix font errors & some tags * web: improve contact & invitation page layout and header * update * remove unused files/css * cleanup * fix link page * remove unused font links --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> * show contact address as is --------- Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com> Co-authored-by: M. Sarmad Qadeer <MSarmadQadeer@gmail.com>
177 lines
7.5 KiB
Haskell
177 lines
7.5 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Static where
|
|
|
|
import Control.Logger.Simple
|
|
import Control.Monad
|
|
import Data.ByteString (ByteString)
|
|
import qualified Data.ByteString as B
|
|
import Data.Maybe (fromMaybe)
|
|
import Data.String (fromString)
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
import Network.Wai.Application.Static as S
|
|
import Network.Wai.Handler.Warp as W
|
|
import qualified Network.Wai.Handler.WarpTLS as W
|
|
import Simplex.Messaging.Encoding.String (strEncode)
|
|
import Simplex.Messaging.Server.Information
|
|
import Simplex.Messaging.Server.Main (EmbeddedWebParams (..), WebHttpsParams (..))
|
|
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
|
import Simplex.Messaging.Util (tshow)
|
|
import Static.Embedded as E
|
|
import System.Directory (createDirectoryIfMissing)
|
|
import System.FilePath
|
|
import UnliftIO.Concurrent (forkFinally)
|
|
|
|
serveStaticFiles :: EmbeddedWebParams -> IO ()
|
|
serveStaticFiles EmbeddedWebParams {webStaticPath, webHttpPort, webHttpsParams} = do
|
|
forM_ webHttpPort $ \port -> flip forkFinally (\e -> logError $ "HTTP server crashed: " <> tshow e) $ do
|
|
logInfo $ "Serving static site on port " <> tshow port
|
|
W.runSettings (mkSettings port) (S.staticApp $ S.defaultFileServerSettings webStaticPath)
|
|
forM_ webHttpsParams $ \WebHttpsParams {port, cert, key} -> flip forkFinally (\e -> logError $ "HTTPS server crashed: " <> tshow e) $ do
|
|
logInfo $ "Serving static site on port " <> tshow port <> " (TLS)"
|
|
W.runTLS (W.tlsSettings cert key) (mkSettings port) (S.staticApp $ S.defaultFileServerSettings webStaticPath)
|
|
where
|
|
mkSettings port = setPort port defaultSettings
|
|
|
|
generateSite :: ServerInformation -> Maybe TransportHost -> FilePath -> IO ()
|
|
generateSite si onionHost sitePath = do
|
|
createDirectoryIfMissing True sitePath
|
|
B.writeFile (sitePath </> "index.html") $ serverInformation si onionHost
|
|
createDirectoryIfMissing True $ sitePath </> "media"
|
|
forM_ E.mediaContent $ \(path, bs) -> B.writeFile (sitePath </> "media" </> path) bs
|
|
createDirectoryIfMissing True $ sitePath </> "contact"
|
|
B.writeFile (sitePath </> "contact" </> "index.html") E.linkHtml
|
|
createDirectoryIfMissing True $ sitePath </> "invitation"
|
|
B.writeFile (sitePath </> "invitation" </> "index.html") E.linkHtml
|
|
logInfo $ "Generated static site contents at " <> tshow sitePath
|
|
|
|
serverInformation :: ServerInformation -> Maybe TransportHost -> ByteString
|
|
serverInformation ServerInformation {config, information} onionHost = render E.indexHtml substs
|
|
where
|
|
substs = substConfig <> maybe [] substInfo information <> [("onionHost", strEncode <$> onionHost)]
|
|
substConfig =
|
|
[ ( "persistence",
|
|
Just $ case persistence config of
|
|
SPMMemoryOnly -> "In-memory only"
|
|
SPMQueues -> "Queues"
|
|
SPMMessages -> "Queues and messages"
|
|
),
|
|
("messageExpiration", Just $ maybe "Never" (fromString . timedTTLText) $ messageExpiration config),
|
|
("statsEnabled", Just . yesNo $ statsEnabled config),
|
|
("newQueuesAllowed", Just . yesNo $ newQueuesAllowed config),
|
|
("basicAuthEnabled", Just . yesNo $ basicAuthEnabled config)
|
|
]
|
|
yesNo True = "Yes"
|
|
yesNo False = "No"
|
|
substInfo spi =
|
|
concat
|
|
[ basic,
|
|
maybe [("usageConditions", Nothing), ("usageAmendments", Nothing)] conds (usageConditions spi),
|
|
maybe [("operator", Nothing)] operatorE (operator spi),
|
|
maybe [("admin", Nothing)] admin (adminContacts spi),
|
|
maybe [("complaints", Nothing)] complaints (complaintsContacts spi),
|
|
maybe [("hosting", Nothing)] hostingE (hosting spi),
|
|
server
|
|
]
|
|
where
|
|
basic =
|
|
[ ("sourceCode", Just . encodeUtf8 $ sourceCode spi),
|
|
("website", encodeUtf8 <$> website spi)
|
|
]
|
|
conds ServerConditions {conditions, amendments} =
|
|
[ ("usageConditions", Just $ encodeUtf8 conditions),
|
|
("usageAmendments", encodeUtf8 <$> amendments)
|
|
]
|
|
operatorE Entity {name, country} =
|
|
[ ("operator", Just ""),
|
|
("operatorEntity", Just $ encodeUtf8 name),
|
|
("operatorCountry", encodeUtf8 <$> country)
|
|
]
|
|
admin ServerContactAddress {simplex, email, pgp} =
|
|
[ ("admin", Just ""),
|
|
("adminSimplex", strEncode <$> simplex),
|
|
("adminEmail", encodeUtf8 <$> email),
|
|
("adminPGP", encodeUtf8 . pkURI <$> pgp),
|
|
("adminPGPFingerprint", encodeUtf8 . pkFingerprint <$> pgp)
|
|
]
|
|
complaints ServerContactAddress {simplex, email, pgp} =
|
|
[ ("complaints", Just ""),
|
|
("complaintsSimplex", strEncode <$> simplex),
|
|
("complaintsEmail", encodeUtf8 <$> email),
|
|
("complaintsPGP", encodeUtf8 . pkURI <$> pgp),
|
|
("complaintsPGPFingerprint", encodeUtf8 . pkFingerprint <$> pgp)
|
|
]
|
|
hostingE Entity {name, country} =
|
|
[ ("hosting", Just ""),
|
|
("hostingEntity", Just $ encodeUtf8 name),
|
|
("hostingCountry", encodeUtf8 <$> country)
|
|
]
|
|
server =
|
|
[ ("serverCountry", fmap encodeUtf8 $ serverCountry =<< information)
|
|
]
|
|
|
|
-- Copy-pasted from simplex-chat Simplex.Chat.Types.Preferences
|
|
{-# INLINE timedTTLText #-}
|
|
timedTTLText :: (Integral i, Show i) => i -> String
|
|
timedTTLText 0 = "0 sec"
|
|
timedTTLText ttl = do
|
|
let (m', s) = ttl `quotRem` 60
|
|
(h', m) = m' `quotRem` 60
|
|
(d', h) = h' `quotRem` 24
|
|
(mm, d) = d' `quotRem` 30
|
|
unwords $
|
|
[mms mm | mm /= 0]
|
|
<> [ds d | d /= 0]
|
|
<> [hs h | h /= 0]
|
|
<> [ms m | m /= 0]
|
|
<> [ss s | s /= 0]
|
|
where
|
|
ss s = show s <> " sec"
|
|
ms m = show m <> " min"
|
|
hs 1 = "1 hour"
|
|
hs h = show h <> " hours"
|
|
ds 1 = "1 day"
|
|
ds 7 = "1 week"
|
|
ds 14 = "2 weeks"
|
|
ds d = show d <> " days"
|
|
mms 1 = "1 month"
|
|
mms mm = show mm <> " months"
|
|
|
|
-- | Rewrite source with provided substitutions
|
|
render :: ByteString -> [(ByteString, Maybe ByteString)] -> ByteString
|
|
render src = \case
|
|
[] -> src
|
|
(label, content') : rest -> render (section_ label content' src) rest
|
|
|
|
-- | Rewrite section content inside @<x-label>...</x-label>@ markers.
|
|
-- Markers are always removed when found. Closing marker is mandatory.
|
|
-- If content is absent, whole section is removed.
|
|
-- Section content is delegated to `item_`. If no sections found, the whole source is delegated.
|
|
section_ :: ByteString -> Maybe ByteString -> ByteString -> ByteString
|
|
section_ label content' src =
|
|
case B.breakSubstring startMarker src of
|
|
(_, "") -> item_ label (fromMaybe "" content') src -- no section, just replace items
|
|
(before, afterStart') ->
|
|
-- found section start, search for end too
|
|
case B.breakSubstring endMarker $ B.drop (B.length startMarker) afterStart' of
|
|
(_, "") -> error $ "missing section end: " <> show endMarker
|
|
(inside, next') ->
|
|
let next = B.drop (B.length endMarker) next'
|
|
in case content' of
|
|
Nothing -> before <> next -- collapse section
|
|
Just content -> before <> item_ label content inside <> section_ label content' next
|
|
where
|
|
startMarker = "<x-" <> label <> ">"
|
|
endMarker = "</x-" <> label <> ">"
|
|
|
|
-- | Replace all occurences of @${label}@ with provided content.
|
|
item_ :: ByteString -> ByteString -> ByteString -> ByteString
|
|
item_ label content' src =
|
|
case B.breakSubstring marker src of
|
|
(done, "") -> done
|
|
(before, after') -> before <> content' <> item_ label content' (B.drop (B.length marker) after')
|
|
where
|
|
marker = "${" <> label <> "}"
|