2012-07-26 01:26:13 +00:00
|
|
|
{- git-annex assistant webapp
|
|
|
|
-
|
|
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}
|
2012-07-29 01:21:22 +00:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
2012-07-26 01:26:13 +00:00
|
|
|
|
|
|
|
module Assistant.Threads.WebApp where
|
|
|
|
|
|
|
|
import Assistant.Common
|
2012-07-26 03:13:01 +00:00
|
|
|
import Assistant.ThreadedMonad
|
2012-07-26 01:26:13 +00:00
|
|
|
import Assistant.DaemonStatus
|
2012-07-27 15:47:34 +00:00
|
|
|
import Assistant.TransferQueue
|
2012-07-29 01:21:22 +00:00
|
|
|
import Utility.NotificationBroadcaster
|
2012-07-26 01:26:13 +00:00
|
|
|
import Utility.WebApp
|
2012-07-26 06:45:01 +00:00
|
|
|
import Utility.Yesod
|
2012-07-26 07:38:20 +00:00
|
|
|
import Utility.FileMode
|
|
|
|
import Utility.TempFile
|
2012-07-26 06:45:01 +00:00
|
|
|
import Git
|
2012-07-27 15:47:34 +00:00
|
|
|
import Logs.Transfer
|
|
|
|
import Utility.Percentage
|
|
|
|
import Utility.DataUnits
|
|
|
|
import Types.Key
|
|
|
|
import qualified Remote
|
2012-07-26 01:26:13 +00:00
|
|
|
|
|
|
|
import Yesod
|
2012-07-26 06:45:01 +00:00
|
|
|
import Yesod.Static
|
2012-07-26 03:50:29 +00:00
|
|
|
import Text.Hamlet
|
2012-07-26 03:13:01 +00:00
|
|
|
import Network.Socket (PortNumber)
|
2012-07-26 07:38:20 +00:00
|
|
|
import Text.Blaze.Renderer.String
|
2012-07-27 15:47:34 +00:00
|
|
|
import Data.Text (Text, pack, unpack)
|
2012-07-26 21:56:24 +00:00
|
|
|
import Data.Time.Clock
|
2012-07-27 15:47:34 +00:00
|
|
|
import qualified Data.Map as M
|
2012-07-26 08:50:09 +00:00
|
|
|
|
|
|
|
thisThread :: String
|
|
|
|
thisThread = "WebApp"
|
2012-07-26 01:26:13 +00:00
|
|
|
|
2012-07-26 06:45:01 +00:00
|
|
|
data WebApp = WebApp
|
2012-07-27 01:51:56 +00:00
|
|
|
{ threadState :: ThreadState
|
|
|
|
, daemonStatus :: DaemonStatusHandle
|
2012-07-27 15:47:34 +00:00
|
|
|
, transferQueue :: TransferQueue
|
2012-07-26 08:50:09 +00:00
|
|
|
, secretToken :: Text
|
2012-07-26 06:45:01 +00:00
|
|
|
, baseTitle :: String
|
|
|
|
, getStatic :: Static
|
|
|
|
}
|
|
|
|
|
|
|
|
staticFiles "static"
|
2012-07-26 01:26:13 +00:00
|
|
|
|
|
|
|
mkYesod "WebApp" [parseRoutes|
|
|
|
|
/ HomeR GET
|
2012-07-29 01:21:22 +00:00
|
|
|
/status/#NotificationId StatusR GET
|
2012-07-26 01:26:13 +00:00
|
|
|
/config ConfigR GET
|
2012-07-26 09:20:52 +00:00
|
|
|
/static StaticR Static getStatic
|
2012-07-26 01:26:13 +00:00
|
|
|
|]
|
|
|
|
|
2012-07-29 01:21:22 +00:00
|
|
|
instance PathPiece NotificationId where
|
|
|
|
toPathPiece = pack . show
|
|
|
|
fromPathPiece = readish . unpack
|
|
|
|
|
2012-07-26 06:45:01 +00:00
|
|
|
instance Yesod WebApp where
|
2012-07-27 08:48:50 +00:00
|
|
|
defaultLayout widget = do
|
2012-07-26 06:45:01 +00:00
|
|
|
mmsg <- getMessage
|
|
|
|
webapp <- getYesod
|
2012-07-27 08:48:50 +00:00
|
|
|
page <- widgetToPageContent $ do
|
|
|
|
addStylesheet $ StaticR css_bootstrap_css
|
|
|
|
addStylesheet $ StaticR css_bootstrap_responsive_css
|
|
|
|
addScript $ StaticR jquery_full_js
|
|
|
|
addScript $ StaticR js_bootstrap_dropdown_js
|
2012-07-27 20:28:00 +00:00
|
|
|
addScript $ StaticR js_bootstrap_alert_js
|
2012-07-28 00:47:48 +00:00
|
|
|
$(widgetFile "page")
|
2012-07-27 08:48:50 +00:00
|
|
|
hamletToRepHtml $(hamletFile $ hamletTemplate "bootstrap")
|
2012-07-26 01:26:13 +00:00
|
|
|
|
2012-07-26 08:50:09 +00:00
|
|
|
{- Require an auth token be set when accessing any (non-static route) -}
|
|
|
|
isAuthorized _ _ = checkAuthToken secretToken
|
|
|
|
|
|
|
|
{- Add the auth token to every url generated, except static subsite
|
|
|
|
- urls (which can show up in Permission Denied pages). -}
|
|
|
|
joinPath = insertAuthToken secretToken excludeStatic
|
|
|
|
where
|
|
|
|
excludeStatic [] = True
|
|
|
|
excludeStatic (p:_) = p /= "static"
|
|
|
|
|
2012-07-26 16:41:20 +00:00
|
|
|
makeSessionBackend = webAppSessionBackend
|
2012-07-26 21:56:24 +00:00
|
|
|
jsLoader _ = BottomOfHeadBlocking
|
2012-07-26 16:10:53 +00:00
|
|
|
|
2012-07-27 01:03:46 +00:00
|
|
|
{- Add to any widget to make it auto-update.
|
|
|
|
-
|
2012-07-27 08:48:50 +00:00
|
|
|
- The widget should have a html element with id=updating, which will be
|
2012-07-27 01:03:46 +00:00
|
|
|
- replaced when it's updated.
|
|
|
|
-
|
|
|
|
- Updating is done by getting html from the gethtml route.
|
|
|
|
- Or, the home route is used if the whole page has to be refreshed to
|
|
|
|
- update.
|
|
|
|
-
|
|
|
|
- ms_delay is how long to delay between updates.
|
|
|
|
- ms_startdelay is how long to delay before updating the widget at the
|
|
|
|
- state.
|
|
|
|
-}
|
|
|
|
autoUpdate :: Text -> Route WebApp -> Route WebApp -> Int -> Int -> Widget
|
2012-07-27 08:48:50 +00:00
|
|
|
autoUpdate updating gethtml home ms_delay ms_startdelay = do
|
2012-07-27 01:03:46 +00:00
|
|
|
{- Fallback refreshing is provided for non-javascript browsers. -}
|
|
|
|
let delayseconds = show $ ms_to_seconds ms_delay
|
|
|
|
toWidgetHead $(hamletFile $ hamletTemplate "metarefresh")
|
|
|
|
|
|
|
|
{- Use long polling to update the status display. -}
|
|
|
|
let delay = show ms_delay
|
|
|
|
let startdelay = show ms_startdelay
|
2012-07-27 02:54:31 +00:00
|
|
|
$(widgetFile "longpolling")
|
2012-07-27 01:03:46 +00:00
|
|
|
where
|
|
|
|
ms_to_seconds :: Int -> Int
|
|
|
|
ms_to_seconds ms = ceiling ((fromIntegral ms :: Double) / 1000)
|
|
|
|
|
2012-07-29 01:21:22 +00:00
|
|
|
{- A dynamically updating status display. -}
|
2012-07-27 01:03:46 +00:00
|
|
|
statusDisplay :: Widget
|
|
|
|
statusDisplay = do
|
|
|
|
webapp <- lift getYesod
|
|
|
|
time <- show <$> liftIO getCurrentTime
|
|
|
|
|
2012-07-27 15:47:34 +00:00
|
|
|
current <- liftIO $ runThreadState (threadState webapp) $
|
|
|
|
M.toList . currentTransfers
|
2012-07-28 22:02:11 +00:00
|
|
|
<$> liftIO (getDaemonStatus $ daemonStatus webapp)
|
2012-07-27 15:47:34 +00:00
|
|
|
queued <- liftIO $ getTransferQueue $ transferQueue webapp
|
|
|
|
let transfers = current ++ queued
|
|
|
|
|
2012-07-27 08:48:50 +00:00
|
|
|
updating <- lift newIdent
|
2012-07-27 02:54:31 +00:00
|
|
|
$(widgetFile "status")
|
2012-07-27 01:03:46 +00:00
|
|
|
|
2012-07-29 01:21:22 +00:00
|
|
|
nid <- liftIO $ notificationHandleToId <$>
|
|
|
|
(newNotificationHandle =<< getNotificationBroadcaster webapp)
|
|
|
|
autoUpdate updating (StatusR nid) HomeR (3000 :: Int) (40 :: Int)
|
|
|
|
|
|
|
|
getNotificationBroadcaster :: WebApp -> IO NotificationBroadcaster
|
|
|
|
getNotificationBroadcaster webapp = notificationBroadcaster
|
|
|
|
<$> getDaemonStatus (daemonStatus webapp)
|
2012-07-27 01:03:46 +00:00
|
|
|
|
2012-07-26 01:26:13 +00:00
|
|
|
getHomeR :: Handler RepHtml
|
2012-07-27 08:48:50 +00:00
|
|
|
getHomeR = defaultLayout statusDisplay
|
2012-07-26 21:56:24 +00:00
|
|
|
|
|
|
|
{- Called by client to poll for a new webapp status display.
|
|
|
|
-
|
|
|
|
- Should block until the status has changed, and then return a div
|
|
|
|
- containing the new status, which will be inserted into the calling page.
|
2012-07-27 01:03:46 +00:00
|
|
|
-
|
|
|
|
- Note that the head of the widget is not included, only its
|
|
|
|
- body is. To get the widget head content, the widget is also
|
|
|
|
- inserted onto the getHomeR page.
|
2012-07-26 21:56:24 +00:00
|
|
|
-}
|
2012-07-29 01:21:22 +00:00
|
|
|
getStatusR :: NotificationId -> Handler RepHtml
|
|
|
|
getStatusR nid = do
|
|
|
|
{- Block until there is an updated status to display. -}
|
|
|
|
webapp <- getYesod
|
|
|
|
b <- liftIO $ getNotificationBroadcaster webapp
|
|
|
|
liftIO $ waitNotification $ notificationHandleFromId b nid
|
|
|
|
|
2012-07-27 01:03:46 +00:00
|
|
|
page <- widgetToPageContent statusDisplay
|
|
|
|
hamletToRepHtml $ [hamlet|^{pageBody page}|]
|
2012-07-26 01:26:13 +00:00
|
|
|
|
|
|
|
getConfigR :: Handler RepHtml
|
2012-07-26 06:45:01 +00:00
|
|
|
getConfigR = defaultLayout $ do
|
|
|
|
setTitle "configuration"
|
2012-07-26 09:20:52 +00:00
|
|
|
[whamlet|<a href="@{HomeR}">main|]
|
2012-07-26 01:26:13 +00:00
|
|
|
|
2012-07-27 19:33:24 +00:00
|
|
|
webAppThread :: ThreadState -> DaemonStatusHandle -> TransferQueue -> Maybe (IO ()) -> IO ()
|
|
|
|
webAppThread st dstatus transferqueue onstartup = do
|
2012-07-27 15:47:34 +00:00
|
|
|
webapp <- mkWebApp
|
2012-07-27 03:55:51 +00:00
|
|
|
app <- toWaiAppPlain webapp
|
2012-07-26 01:26:13 +00:00
|
|
|
app' <- ifM debugEnabled
|
|
|
|
( return $ httpDebugLogger app
|
|
|
|
, return app
|
|
|
|
)
|
2012-07-27 19:33:24 +00:00
|
|
|
runWebApp app' $ \port -> do
|
|
|
|
runThreadState st $ writeHtmlShim webapp port
|
|
|
|
maybe noop id onstartup
|
2012-07-27 15:47:34 +00:00
|
|
|
where
|
|
|
|
mkWebApp = do
|
|
|
|
dir <- absPath =<< runThreadState st (fromRepo repoPath)
|
|
|
|
home <- myHomeDir
|
|
|
|
let reldir = if dirContains home dir
|
|
|
|
then relPathDirToFile home dir
|
|
|
|
else dir
|
|
|
|
token <- genRandomToken
|
|
|
|
return $ WebApp
|
|
|
|
{ threadState = st
|
|
|
|
, daemonStatus = dstatus
|
|
|
|
, transferQueue = transferqueue
|
|
|
|
, secretToken = pack token
|
|
|
|
, baseTitle = reldir
|
|
|
|
, getStatic = $(embed "static")
|
|
|
|
}
|
2012-07-26 03:13:01 +00:00
|
|
|
|
2012-07-26 08:50:09 +00:00
|
|
|
{- Creates a html shim file that's used to redirect into the webapp,
|
|
|
|
- to avoid exposing the secretToken when launching the web browser. -}
|
2012-07-26 07:38:20 +00:00
|
|
|
writeHtmlShim :: WebApp -> PortNumber -> Annex ()
|
|
|
|
writeHtmlShim webapp port = do
|
2012-07-26 08:50:09 +00:00
|
|
|
liftIO $ debug thisThread ["running on port", show port]
|
2012-07-26 03:13:01 +00:00
|
|
|
htmlshim <- fromRepo gitAnnexHtmlShim
|
2012-07-26 07:38:20 +00:00
|
|
|
liftIO $ viaTmp go htmlshim $ genHtmlShim webapp port
|
|
|
|
where
|
|
|
|
go file content = do
|
|
|
|
h <- openFile file WriteMode
|
|
|
|
modifyFileMode file $ removeModes [groupReadMode, otherReadMode]
|
|
|
|
hPutStr h content
|
|
|
|
hClose h
|
2012-07-26 03:13:01 +00:00
|
|
|
|
|
|
|
{- TODO: generate this static file using Yesod. -}
|
2012-07-26 07:38:20 +00:00
|
|
|
genHtmlShim :: WebApp -> PortNumber -> String
|
|
|
|
genHtmlShim webapp port = renderHtml $(shamletFile $ hamletTemplate "htmlshim")
|
2012-07-26 01:26:13 +00:00
|
|
|
where
|
2012-07-26 08:50:09 +00:00
|
|
|
url = "http://localhost:" ++ show port ++
|
|
|
|
"/?auth=" ++ unpack (secretToken webapp)
|