{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- the ToHtml class also provides a method without escaping which we don't use, -- so it's safe to never define it {-# OPTIONS_GHC -Wno-missing-methods #-} {-# OPTIONS_GHC -Wno-orphans #-} -- | Module containing orphan instances of Lucid's ToHtml, used for rendering -- linter results as html module HtmlOrphans () where import Universum import CheckDir (DirResult (..), MissingAsset (MissingAsset), MissingDep (..), maximumLintLevel) import CheckMap (MapResult (..)) import Data.List.Extra (escapeJSON) import qualified Data.Map as M import Handlers (AdminOverview (..)) import Lucid (HtmlT, ToHtml) import Lucid.Base (ToHtml (toHtml)) import Lucid.Html5 (a_, body_, button_, class_, code_, disabled_, div_, em_, h1_, h2_, h3_, h4_, h5_, head_, href_, html_, id_, li_, link_, main_, onclick_, p_, rel_, script_, span_, src_, title_, type_, ul_) import Server (JobStatus (..), Org (orgSlug), RemoteRef (reporef, repourl), prettySha, unState) import Types (Hint (Hint), Level (..)) import Fmt mono :: Monad m => HtmlT m () -> HtmlT m () mono = code_ [class_ "small text-muted"] htmldoc :: Monad m => HtmlT m () -> HtmlT m () htmldoc inner = html_ $ do head_ $ do title_ "Job Status" link_ [rel_ "stylesheet", type_ "text/css", href_ "/bootstrap.min.css" ] link_ [rel_ "stylesheet", type_ "text/css", href_ "/style.css" ] body_ $ main_ [class_ "main-content"] inner instance ToHtml JobStatus where toHtml status = htmldoc $ case status of Pending _ -> do h1_ "Pending …" p_ "(please note that this site won't auto-reload, you'll have to refresh it yourself)" autoReloadScript Linted res _rev (pending, _) -> do h1_ "Linter Result" p_ $ do "your map will be re-linted periodically. " if pending then button_ [class_ "btn btn-primary btn-disabled", disabled_ "true"] "pending …" else button_ [onclick_ "relint()", class_ "btn btn-primary", id_ "relint_button"] "relint now" toHtml res script_ "function relint() {\n\ \ var xhr = new XMLHttpRequest ();\n\ \ xhr.open('POST', 'relint', true);\n\ \ xhr.onreadystatechange = (e) => {if (xhr.status == 200) {\n\ \ console.log(e);\n\ \ }}\n\ \ xhr.send(null);\n\ \}" autoReloadScript Failed err -> do h1_ "System Error" p_ $ "error: " <> toHtml err p_ "you should probably ping an admin about this or sth" where autoReloadScript = script_ "let proto = window.location.protocol === 'https://' ? 'wss' : 'ws://';\ \let ws = new WebSocket(proto + window.location.host + window.location.pathname + 'realtime');\n\ \ws.onmessage = (event) => {\n\ \ let resp = JSON.parse(event.data);\n\ \ if (resp == 'RelintPending') {\n\ \ let btn = document.getElementById('relint_button');\n\ \ btn.innerText = 'pending …';\n\ \ btn.disabled = true;\n\ \ btn.class = 'btn btn-disabled';\n\ \ } else if (resp == 'Reload') {\n\ \ location.reload();\n\ \ }\n\ \}" instance ToHtml AdminOverview where toHtml (AdminOverview state) = htmldoc $ do h1_ "Map List" forM_ (view unState state) $ \(org, jobs) -> do h2_ (toHtml $ orgSlug org) if null jobs then em_ "(nothing yet)" else flip M.foldMapWithKey jobs $ \sha1 (ref, status, _lastvalid) -> li_ $ do case status of Pending _ -> badge Info "pending" (Linted res rev _) -> toHtml $ maximumLintLevel res (Failed _) -> badge Error "system error" " "; a_ [href_ ("/status/"+|orgSlug org|+"/"+|prettySha sha1|+"/")] $ do mono $ toHtml $ reporef ref; " on "; mono $ toHtml $ repourl ref badge :: Monad m => Level -> HtmlT m () -> HtmlT m () badge level = span_ [class_ badgetype] where badgetype = case level of Info -> "badge badge-info" Suggestion -> "badge badge-info" Warning -> "badge badge-warning" Forbidden -> "badge badge-danger" Error -> "badge badge-danger" Fatal -> "badge badge-danger" -- | Lint Levels directly render into badges instance ToHtml Level where toHtml level = do badge level (show level); " " -- | Hints are just text with a level instance ToHtml Hint where toHtml (Hint level msg) = do toHtml level; " "; toHtml msg headerText :: Monad m => Level -> HtmlT m () headerText = \case Info -> "Couldn't find a thing to complain about. Congratulations!" Suggestion -> "There's a couple smaller nitpicks; maybe take a look at those?" Warning -> "The map is fine, but some things look like they might be mistakes; \ \perhaps you want to take a look at those?" Forbidden -> "The map is fine in principle, but contains things that are not\ \allowed at this event" Error -> "Your map currently contains errors and should probably be fixed" Fatal -> "Something broke while linting; if you're not sure why or how to make \ \it work, feel free to tell an admin about it" -- | The fully monky instance ToHtml (DirResult a) where toHtml res@DirResult { .. } = do p_ $ do badge maxlevel "Linted:"; " "; headerText maxlevel h3_ "Exits" unless (null dirresultDeps) $ ul_ $ forM_ dirresultDeps $ \missing -> do li_ $ do -- TODO: the whole Maybe Bool thing is annoying; I think that was a -- remnant of talking to python stuff and can probably be removed? if depFatal missing == Just True then do { toHtml Error; "Map " } else do { toHtml Warning; "Entrypoint " } code_ $ toHtml (entrypoint missing) " does not exist" unless (depFatal missing /= Just True) $ do " (no layer with that name is a "; mono "startLayer"; ")" ", but is used as "; mono "exitUrl"; " in " placeList (neededBy missing); "." -- the exit graph thing script_ [ src_ "/dot-wasm.js" ] (""::Text) script_ [ src_ "/d3.js" ] (""::Text) script_ [ src_ "/d3-graphviz.js" ] (""::Text) div_ [ id_ "exitGraph" ] "" script_ $ "\ \d3.select(\"#exitGraph\")\n\ \ .graphviz()\n\ \ .dot(\"" <> toText (escapeJSON $ toString dirresultGraph) <> "\")\n\ \ .render()\n\ \" unless (null dirresultMissingAssets) $ do h2_ [class_ "border-bottom"] "Assets" ul_ $ forM_ dirresultMissingAssets $ \(MissingAsset MissingDep { .. }) -> li_ $ do toHtml Error; "File "; mono $ toHtml entrypoint " does not exist, but is referenced in "; placeList neededBy; ")" unless (null dirresultMaps) $ do h3_ "Maps" flip M.foldMapWithKey dirresultMaps $ \name MapResult { .. } -> do h4_ (toHtml name) ul_ $ forM_ mapresultGeneral $ \lint -> li_ (toHtml lint) unless (null mapresultLayer) $ do h5_ "Layers" ul_ (listMapWithKey mapresultLayer) unless (null mapresultTileset) $ do h5_ "Tilesets" ul_ (listMapWithKey mapresultTileset) where maxlevel = maximumLintLevel res placeList :: (Monad m, ToHtml a) => [a] -> HtmlT m () placeList occurances = sequence_ . intersperse ", " $ occurances <&> \place -> code_ [class_ "small text-muted"] (toHtml place) listMapWithKey map = flip M.foldMapWithKey map $ \lint places -> li_ $ do toHtml lint; " (in "; placeList places; ")"