1087fac544
Based on feedback that it was too sparse to be clear what it's doing.
237 lines
7.6 KiB
Haskell
237 lines
7.6 KiB
Haskell
{- git-annex assistant webapp dashboard
|
|
-
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
-
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
-}
|
|
|
|
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
|
|
|
module Assistant.WebApp.DashBoard where
|
|
|
|
import Assistant.Common
|
|
import Assistant.WebApp
|
|
import Assistant.WebApp.Types
|
|
import Assistant.WebApp.SideBar
|
|
import Assistant.WebApp.Notifications
|
|
import Assistant.WebApp.Configurators
|
|
import Assistant.DaemonStatus
|
|
import Assistant.TransferQueue
|
|
import Assistant.TransferSlots
|
|
import qualified Assistant.Threads.Transferrer as Transferrer
|
|
import Utility.NotificationBroadcaster
|
|
import Utility.Yesod
|
|
import Logs.Transfer
|
|
import Utility.Percentage
|
|
import Utility.DataUnits
|
|
import Types.Key
|
|
import qualified Remote
|
|
import qualified Git
|
|
import Locations.UserConfig
|
|
|
|
import Yesod
|
|
import Text.Hamlet
|
|
import qualified Data.Map as M
|
|
import Control.Concurrent
|
|
import System.Posix.Signals (signalProcessGroup, sigTERM, sigKILL)
|
|
import System.Posix.Process (getProcessGroupIDOf)
|
|
|
|
{- A display of currently running and queued transfers.
|
|
-
|
|
- Or, if there have never been any this run, an intro display. -}
|
|
transfersDisplay :: Bool -> Widget
|
|
transfersDisplay warnNoScript = do
|
|
webapp <- lift getYesod
|
|
current <- lift $ M.toList <$> getCurrentTransfers
|
|
queued <- liftIO $ getTransferQueue $ transferQueue webapp
|
|
autoUpdate ident NotifierTransfersR (10 :: Int) (10 :: Int)
|
|
let transfers = simplifyTransfers $ current ++ queued
|
|
if null transfers
|
|
then ifM (lift $ showIntro <$> getWebAppState)
|
|
( introDisplay ident
|
|
, $(widgetFile "dashboard/transfers")
|
|
)
|
|
else $(widgetFile "dashboard/transfers")
|
|
where
|
|
ident = "transfers"
|
|
isrunning info = not $
|
|
transferPaused info || isNothing (startedTime info)
|
|
|
|
{- Simplifies a list of transfers, avoiding display of redundant
|
|
- equivilant transfers. -}
|
|
simplifyTransfers :: [(Transfer, TransferInfo)] -> [(Transfer, TransferInfo)]
|
|
simplifyTransfers [] = []
|
|
simplifyTransfers (x:[]) = [x]
|
|
simplifyTransfers (v@(t1, _):r@((t2, _):l))
|
|
| equivilantTransfer t1 t2 = simplifyTransfers (v:l)
|
|
| otherwise = v : (simplifyTransfers r)
|
|
|
|
{- Called by client to get a display of currently in process transfers.
|
|
-
|
|
- Returns a div, which will be inserted into the calling page.
|
|
-
|
|
- 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.
|
|
-}
|
|
getTransfersR :: NotificationId -> Handler RepHtml
|
|
getTransfersR nid = do
|
|
waitNotifier transferNotifier nid
|
|
|
|
page <- widgetToPageContent $ transfersDisplay False
|
|
hamletToRepHtml $ [hamlet|^{pageBody page}|]
|
|
|
|
{- The main dashboard. -}
|
|
dashboard :: Bool -> Widget
|
|
dashboard warnNoScript = do
|
|
sideBarDisplay
|
|
let content = transfersDisplay warnNoScript
|
|
$(widgetFile "dashboard/main")
|
|
|
|
getHomeR :: Handler RepHtml
|
|
getHomeR = ifM (inFirstRun)
|
|
( redirect ConfigR
|
|
, bootstrap (Just DashBoard) $ dashboard True
|
|
)
|
|
|
|
{- Used to test if the webapp is running. -}
|
|
headHomeR :: Handler ()
|
|
headHomeR = noop
|
|
|
|
{- Same as HomeR, except no autorefresh at all (and no noscript warning). -}
|
|
getNoScriptR :: Handler RepHtml
|
|
getNoScriptR = bootstrap (Just DashBoard) $ dashboard False
|
|
|
|
{- Same as HomeR, except with autorefreshing via meta refresh. -}
|
|
getNoScriptAutoR :: Handler RepHtml
|
|
getNoScriptAutoR = bootstrap (Just DashBoard) $ do
|
|
let ident = NoScriptR
|
|
let delayseconds = 3 :: Int
|
|
let this = NoScriptAutoR
|
|
toWidgetHead $(hamletFile $ hamletTemplate "dashboard/metarefresh")
|
|
dashboard False
|
|
|
|
{- The javascript code does a post. -}
|
|
postFileBrowserR :: Handler ()
|
|
postFileBrowserR = void openFileBrowser
|
|
|
|
{- Used by non-javascript browsers, where clicking on the link actually
|
|
- opens this page, so we redirect back to the referrer. -}
|
|
getFileBrowserR :: Handler ()
|
|
getFileBrowserR = whenM openFileBrowser $ redirectBack
|
|
|
|
{- Opens the system file browser on the repo, or, as a fallback,
|
|
- goes to a file:// url. Returns True if it's ok to redirect away
|
|
- from the page (ie, the system file browser was opened).
|
|
-
|
|
- Note that the command is opened using a different thread, to avoid
|
|
- blocking the response to the browser on it. -}
|
|
openFileBrowser :: Handler Bool
|
|
openFileBrowser = do
|
|
path <- runAnnex (error "no configured repository") $
|
|
fromRepo Git.repoPath
|
|
ifM (liftIO $ inPath cmd <&&> inPath cmd)
|
|
( do
|
|
void $ liftIO $ forkIO $ void $
|
|
boolSystem cmd [Param path]
|
|
return True
|
|
, do
|
|
clearUltDest
|
|
setUltDest $ "file://" ++ path
|
|
void $ redirectUltDest HomeR
|
|
return False
|
|
)
|
|
where
|
|
#if OSX
|
|
cmd = "open"
|
|
#else
|
|
cmd = "xdg-open"
|
|
#endif
|
|
|
|
{- Transfer controls. The GET is done in noscript mode and redirects back
|
|
- to the referring page. The POST is called by javascript. -}
|
|
getPauseTransferR :: Transfer -> Handler ()
|
|
getPauseTransferR t = pauseTransfer t >> redirectBack
|
|
postPauseTransferR :: Transfer -> Handler ()
|
|
postPauseTransferR t = pauseTransfer t
|
|
getStartTransferR :: Transfer -> Handler ()
|
|
getStartTransferR t = startTransfer t >> redirectBack
|
|
postStartTransferR :: Transfer -> Handler ()
|
|
postStartTransferR t = startTransfer t
|
|
getCancelTransferR :: Transfer -> Handler ()
|
|
getCancelTransferR t = cancelTransfer False t >> redirectBack
|
|
postCancelTransferR :: Transfer -> Handler ()
|
|
postCancelTransferR t = cancelTransfer False t
|
|
|
|
pauseTransfer :: Transfer -> Handler ()
|
|
pauseTransfer = cancelTransfer True
|
|
|
|
cancelTransfer :: Bool -> Transfer-> Handler ()
|
|
cancelTransfer pause t = do
|
|
webapp <- getYesod
|
|
let dstatus = daemonStatus webapp
|
|
m <- getCurrentTransfers
|
|
liftIO $ do
|
|
unless pause $
|
|
{- remove queued transfer -}
|
|
void $ dequeueTransfers (transferQueue webapp) dstatus $
|
|
equivilantTransfer t
|
|
{- stop running transfer -}
|
|
maybe noop (stop dstatus) (M.lookup t m)
|
|
where
|
|
stop dstatus info = do
|
|
{- When there's a thread associated with the
|
|
- transfer, it's signaled first, to avoid it
|
|
- displaying any alert about the transfer having
|
|
- failed when the transfer process is killed. -}
|
|
maybe noop signalthread $ transferTid info
|
|
maybe noop killproc $ transferPid info
|
|
if pause
|
|
then void $
|
|
alterTransferInfo dstatus t $ \i -> i
|
|
{ transferPaused = True }
|
|
else void $
|
|
removeTransfer dstatus t
|
|
signalthread tid
|
|
| pause = throwTo tid PauseTransfer
|
|
| otherwise = killThread tid
|
|
{- In order to stop helper processes like rsync,
|
|
- kill the whole process group of the process running the
|
|
- transfer. -}
|
|
killproc pid = do
|
|
g <- getProcessGroupIDOf pid
|
|
void $ tryIO $ signalProcessGroup sigTERM g
|
|
threadDelay 50000 -- 0.05 second grace period
|
|
void $ tryIO $ signalProcessGroup sigKILL g
|
|
|
|
startTransfer :: Transfer -> Handler ()
|
|
startTransfer t = do
|
|
m <- getCurrentTransfers
|
|
maybe startqueued go (M.lookup t m)
|
|
where
|
|
go info = maybe (start info) resume $ transferTid info
|
|
startqueued = do
|
|
webapp <- getYesod
|
|
let dstatus = daemonStatus webapp
|
|
let q = transferQueue webapp
|
|
is <- liftIO $ map snd <$> getMatchingTransfers q dstatus (== t)
|
|
maybe noop start $ headMaybe is
|
|
resume tid = do
|
|
webapp <- getYesod
|
|
let dstatus = daemonStatus webapp
|
|
liftIO $ do
|
|
alterTransferInfo dstatus t $ \i -> i
|
|
{ transferPaused = False }
|
|
throwTo tid ResumeTransfer
|
|
start info = do
|
|
webapp <- getYesod
|
|
let st = fromJust $ threadState webapp
|
|
let dstatus = daemonStatus webapp
|
|
let slots = transferSlots webapp
|
|
liftIO $ inImmediateTransferSlot dstatus slots $ do
|
|
program <- readProgramFile
|
|
Transferrer.startTransfer st dstatus program t info
|
|
|
|
getCurrentTransfers :: Handler TransferMap
|
|
getCurrentTransfers = currentTransfers
|
|
<$> (liftIO . getDaemonStatus =<< daemonStatus <$> getYesod)
|