mirror of https://github.com/voidlizard/hbs2
hbs2-dashboard and hbs2-fixer removed
This commit is contained in:
parent
57b480a454
commit
96b5b051b3
|
@ -34,10 +34,7 @@ outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
|||
"hbs2-core"
|
||||
"hbs2-storage-simple"
|
||||
"hbs2-git"
|
||||
"hbs2-git-dashboard"
|
||||
"hbs2-git3"
|
||||
"hbs2-qblf"
|
||||
"hbs2-fixer"
|
||||
"hbs2-cli"
|
||||
"hbs2-sync"
|
||||
"fixme-new"
|
||||
|
|
|
@ -1,724 +0,0 @@
|
|||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
module Main where
|
||||
|
||||
import HBS2.Prelude.Plated
|
||||
import HBS2.Actors.Peer
|
||||
import HBS2.Base58
|
||||
import HBS2.OrDie
|
||||
import HBS2.Hash
|
||||
import HBS2.Data.Types.Refs
|
||||
import HBS2.Net.Auth.Credentials
|
||||
import HBS2.Polling
|
||||
import HBS2.Misc.PrettyStuff
|
||||
import HBS2.System.Dir
|
||||
import HBS2.System.Logger.Simple.ANSI hiding (info)
|
||||
import HBS2.Net.Messaging.Unix
|
||||
|
||||
import HBS2.Git.Data.LWWBlock
|
||||
|
||||
import HBS2.Net.Proto.Notify
|
||||
import HBS2.Net.Proto.Service
|
||||
import HBS2.Peer.Notify
|
||||
import HBS2.Peer.RPC.API.Peer
|
||||
import HBS2.Peer.RPC.API.RefLog
|
||||
import HBS2.Peer.RPC.API.LWWRef
|
||||
import HBS2.Peer.RPC.API.Storage
|
||||
import HBS2.Peer.RPC.Client.StorageClient
|
||||
|
||||
import HBS2.Peer.CLI.Detect
|
||||
import HBS2.Peer.Proto.RefLog
|
||||
|
||||
import Data.Config.Suckless
|
||||
|
||||
import Data.Time.Clock
|
||||
import Data.Coerce
|
||||
import Control.Monad.Reader
|
||||
import Lens.Micro.Platform
|
||||
import System.Directory
|
||||
import Options.Applicative
|
||||
import Data.Maybe
|
||||
import Data.Either
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Control.Monad.Trans.Cont
|
||||
import Control.Monad.Trans.Maybe
|
||||
import Data.ByteString.Lazy.Char8 qualified as LBS
|
||||
import Data.Text qualified as Text
|
||||
import Data.Hashable
|
||||
import Control.Exception qualified as E
|
||||
import System.Process.Typed
|
||||
import System.Environment qualified as Env
|
||||
import System.Exit qualified as Exit
|
||||
import Data.Cache qualified as Cache
|
||||
import Data.Cache (Cache)
|
||||
|
||||
import System.Exit
|
||||
|
||||
{- HLINT ignore "Functor law" -}
|
||||
|
||||
|
||||
type Config = [Syntax C]
|
||||
|
||||
|
||||
type RLWW = LWWRefKey 'HBS2Basic
|
||||
type RRefLog = RefLogKey 'HBS2Basic
|
||||
|
||||
newtype Watcher =
|
||||
Watcher [Syntax C]
|
||||
deriving newtype (Semigroup,Monoid)
|
||||
|
||||
data Ref =
|
||||
RefRefLog RRefLog
|
||||
| RefLWW RLWW
|
||||
deriving stock (Eq,Generic)
|
||||
|
||||
instance Pretty Ref where
|
||||
pretty (RefRefLog r) = parens $ "reflog" <+> dquotes (pretty r)
|
||||
pretty (RefLWW r) = parens $ "lwwref" <+> dquotes (pretty r)
|
||||
|
||||
newtype AnyPolledRef =
|
||||
AnyPolledRef (PubKey 'Sign 'HBS2Basic)
|
||||
deriving (Eq,Generic)
|
||||
|
||||
instance Hashable AnyPolledRef
|
||||
|
||||
-- FIXME: move-to-suckless-conf
|
||||
deriving newtype instance Hashable Id
|
||||
|
||||
instance Pretty AnyPolledRef where
|
||||
pretty (AnyPolledRef r) = pretty (AsBase58 r)
|
||||
-- deriving newtype instance Pretty (PubKey 'Sign 'HBS2Basic) => Pretty AnyPolledRef
|
||||
|
||||
instance FromStringMaybe AnyPolledRef where
|
||||
fromStringMay = fmap AnyPolledRef . fromStringMay
|
||||
|
||||
newtype PolledRef =
|
||||
PolledRef (Ref, NominalDiffTime)
|
||||
deriving stock (Eq,Generic)
|
||||
deriving newtype (Pretty)
|
||||
|
||||
instance Hashable Ref
|
||||
|
||||
instance Hashable PolledRef where
|
||||
hashWithSalt salt (PolledRef (r,_)) = hashWithSalt salt r
|
||||
|
||||
data FixerEnv = FixerEnv
|
||||
{ _configFile :: Maybe FilePath
|
||||
, _lwwAPI :: ServiceCaller LWWRefAPI UNIX
|
||||
, _refLogAPI :: ServiceCaller RefLogAPI UNIX
|
||||
, _refLogSink :: NotifySink (RefLogEvents L4Proto) UNIX
|
||||
, _peerAPI :: ServiceCaller PeerAPI UNIX
|
||||
, _sto :: AnyStorage
|
||||
, _config :: TVar Config
|
||||
, _configPoll :: TVar Int
|
||||
, _watchers :: TVar (HashMap PolledRef Watcher)
|
||||
, _listeners :: TVar (HashMap RRefLog (Async ()))
|
||||
, _result :: TVar (HashMap Ref (Maybe HashRef, Maybe HashRef))
|
||||
, _runNum :: TVar Int
|
||||
, _locals :: TVar (HashMap Id (Syntax C))
|
||||
, _pipeline :: TQueue (IO ())
|
||||
}
|
||||
|
||||
makeLenses ''FixerEnv
|
||||
|
||||
|
||||
newtype FixerM m a = FixerM { runFixerM :: ReaderT FixerEnv m a }
|
||||
deriving newtype (Applicative, Functor, Monad, MonadIO, MonadReader FixerEnv, MonadUnliftIO)
|
||||
|
||||
instance MonadIO m => HasConf (FixerM m) where
|
||||
getConf = asks _config >>= readTVarIO
|
||||
|
||||
|
||||
debugPrefix = toStdout . logPrefix "[debug] "
|
||||
|
||||
readConf :: MonadIO m => FilePath -> m [Syntax C]
|
||||
readConf fn = liftIO (readFile fn) <&> parseTop <&> fromRight mempty
|
||||
|
||||
withConfig :: MonadUnliftIO m => Maybe FilePath -> FixerM m () -> FixerM m ()
|
||||
withConfig cfgPath m = do
|
||||
defConfDir <- liftIO $ getXdgDirectory XdgConfig "hbs2-fixer"
|
||||
|
||||
let configPath = fromMaybe (defConfDir </> "config") cfgPath
|
||||
unless (isJust cfgPath) do
|
||||
debug $ pretty configPath
|
||||
touch configPath
|
||||
|
||||
syn <- readConf configPath
|
||||
tsyn <- newTVarIO syn
|
||||
|
||||
local (set config tsyn . set configFile (Just configPath)) (void m)
|
||||
|
||||
withApp :: Maybe FilePath -> FixerM IO () -> IO ()
|
||||
withApp cfgPath action = do
|
||||
setLogging @DEBUG debugPrefix
|
||||
setLogging @INFO defLog
|
||||
setLogging @ERROR errorPrefix
|
||||
setLogging @WARN warnPrefix
|
||||
setLogging @NOTICE noticePrefix
|
||||
|
||||
fix \next -> do
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
soname' <- lift detectRPC
|
||||
|
||||
soname <- ContT $ maybe1 soname' (pure ())
|
||||
|
||||
client <- lift $ race (pause @'Seconds 1) (newMessagingUnix False 1.0 soname)
|
||||
>>= orThrowUser ("can't connect to" <+> pretty soname)
|
||||
|
||||
mess <- ContT $ withAsync $ runMessagingUnix client
|
||||
|
||||
peerAPI <- makeServiceCaller @PeerAPI (fromString soname)
|
||||
refLogAPI <- makeServiceCaller @RefLogAPI (fromString soname)
|
||||
storageAPI <- makeServiceCaller @StorageAPI (fromString soname)
|
||||
lwwAPI <- makeServiceCaller @LWWRefAPI (fromString soname)
|
||||
|
||||
let endpoints = [ Endpoint @UNIX peerAPI
|
||||
, Endpoint @UNIX refLogAPI
|
||||
, Endpoint @UNIX lwwAPI
|
||||
, Endpoint @UNIX storageAPI
|
||||
]
|
||||
|
||||
mn <- ContT $ withAsync $ liftIO $ runReaderT (runServiceClientMulti endpoints) client
|
||||
|
||||
let o = [MUDontRetry]
|
||||
clientN <- newMessagingUnixOpts o False 1.0 soname
|
||||
|
||||
notif <- ContT $ withAsync (runMessagingUnix clientN)
|
||||
|
||||
|
||||
sink <- newNotifySink
|
||||
|
||||
void $ ContT $ withAsync $ flip runReaderT clientN $ do
|
||||
debug $ red "notify restarted!"
|
||||
runNotifyWorkerClient sink
|
||||
|
||||
p1 <- ContT $ withAsync $ flip runReaderT clientN $ do
|
||||
runProto @UNIX
|
||||
[ makeResponse (makeNotifyClient @(RefLogEvents L4Proto) sink)
|
||||
]
|
||||
|
||||
env <- FixerEnv Nothing
|
||||
lwwAPI
|
||||
refLogAPI
|
||||
sink
|
||||
peerAPI
|
||||
(AnyStorage (StorageClient storageAPI))
|
||||
<$> newTVarIO mempty
|
||||
<*> newTVarIO 30
|
||||
<*> newTVarIO mempty
|
||||
<*> newTVarIO mempty
|
||||
<*> newTVarIO mempty
|
||||
<*> newTVarIO 0
|
||||
<*> newTVarIO mempty
|
||||
<*> newTQueueIO
|
||||
|
||||
void $ ContT $ bracket (pure ()) $ \_ -> do
|
||||
readTVarIO (_listeners env) <&> HM.elems >>= mapM_ cancel
|
||||
|
||||
p3 <- ContT $ withAsync $ runReaderT (runFixerM $ withConfig cfgPath action) env
|
||||
|
||||
void $ waitAnyCatchCancel [mess,mn,notif,p1,p3]
|
||||
|
||||
debug $ red "respawning..."
|
||||
pause @'Seconds 5
|
||||
next
|
||||
|
||||
setLoggingOff @DEBUG
|
||||
setLoggingOff @INFO
|
||||
setLoggingOff @ERROR
|
||||
setLoggingOff @WARN
|
||||
setLoggingOff @NOTICE
|
||||
|
||||
where
|
||||
errorPrefix = toStdout . logPrefix "[error] "
|
||||
warnPrefix = toStdout . logPrefix "[warn] "
|
||||
noticePrefix = toStdout
|
||||
|
||||
|
||||
data ConfWatch =
|
||||
ConfWatch
|
||||
| ConfRead
|
||||
| ConfUpdate [Syntax C]
|
||||
|
||||
mainLoop :: FixerM IO ()
|
||||
mainLoop = do
|
||||
debug "hbs2-fixer. do stuff since 2024"
|
||||
conf <- getConf
|
||||
-- debug $ line <> vcat (fmap pretty conf)
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
debug $ red "Reloading..."
|
||||
|
||||
lift $ updateFromConfig conf
|
||||
|
||||
p1 <- ContT $ withAsync $ do
|
||||
cfg <- asks _configFile `orDie` "config file not specified"
|
||||
|
||||
flip fix ConfRead $ \next -> \case
|
||||
ConfRead -> do
|
||||
debug $ yellow "read config" <+> pretty cfg
|
||||
newConf <- readConf cfg
|
||||
oldConf <- getConf
|
||||
|
||||
let a = hashObject @HbSync (LBS.pack $ show $ pretty newConf)
|
||||
let b = hashObject @HbSync (LBS.pack $ show $ pretty oldConf)
|
||||
|
||||
let changed = a /= b
|
||||
|
||||
if not changed then
|
||||
next ConfWatch
|
||||
else
|
||||
next (ConfUpdate newConf)
|
||||
|
||||
ConfUpdate new -> do
|
||||
debug $ yellow "read config / update state"
|
||||
updateFromConfig new
|
||||
next ConfWatch
|
||||
|
||||
ConfWatch{} -> do
|
||||
w <- asks _configPoll >>= readTVarIO
|
||||
pause (TimeoutSec (realToFrac w))
|
||||
next ConfRead
|
||||
|
||||
-- poll reflogs
|
||||
p2 <- ContT $ withAsync do
|
||||
|
||||
let w = asks _watchers
|
||||
>>= readTVarIO
|
||||
<&> HM.toList
|
||||
<&> \wtf -> [ (ByFirst r wa, t) | (PolledRef (r,t), wa) <- wtf ]
|
||||
|
||||
polling (Polling 1 1) w $ \case
|
||||
ByFirst ref wa -> do
|
||||
new <- getRefRpc ref
|
||||
re <- asks _result
|
||||
old <- readTVarIO re
|
||||
<&> (snd <=< HM.lookup ref)
|
||||
|
||||
when (new /= old) do
|
||||
atomically $ modifyTVar re (HM.insert ref (old, new))
|
||||
-- bindId
|
||||
forM_ new (runWatcher wa ref)
|
||||
|
||||
pure ()
|
||||
|
||||
jobs <- asks _pipeline
|
||||
p3 <- ContT $ withAsync $ fix \next -> do
|
||||
r <- liftIO $ E.try @SomeException (join $ atomically $ readTQueue jobs)
|
||||
case r of
|
||||
Left e -> do
|
||||
err (viaShow e)
|
||||
let ee = fromException @AsyncCancelled e
|
||||
|
||||
unless (isJust ee) do
|
||||
next
|
||||
|
||||
_ -> next
|
||||
|
||||
void $ waitAnyCatchCancel [p1,p2,p3]
|
||||
|
||||
oneSec :: MonadUnliftIO m => m b -> m (Either () b)
|
||||
oneSec = race (pause @'Seconds 1)
|
||||
|
||||
|
||||
fromStrLitMay :: forall s c . FromStringMaybe s => Syntax c -> Maybe s
|
||||
fromStrLitMay = \case
|
||||
LitStrVal s -> fromStringMay (Text.unpack s)
|
||||
_ -> Nothing
|
||||
|
||||
pattern PTop :: forall {c}. Id -> [Syntax c] -> Syntax c
|
||||
pattern PTop ctor rest <- ListVal (SymbolVal ctor : rest)
|
||||
|
||||
pattern PPolledRef :: forall {c}. Id -> AnyPolledRef -> Syntax c
|
||||
pattern PPolledRef t r <- ListVal [ SymbolVal t, fromStrLitMay @AnyPolledRef -> Just r ]
|
||||
|
||||
pattern PWatchRef :: forall {c}. Integer -> Id -> AnyPolledRef -> [Syntax c] -> [Syntax c]
|
||||
pattern PWatchRef n t r w <- (LitIntVal n : PPolledRef t r : w)
|
||||
|
||||
pattern PListenRef :: forall {c}. Id -> AnyPolledRef -> [Syntax c] -> [Syntax c]
|
||||
pattern PListenRef t r w <- (PPolledRef t r : w)
|
||||
|
||||
-- pattern PDisplay :: Syntax c
|
||||
pattern PDisplay :: forall {c}. Syntax c -> Syntax c
|
||||
pattern PDisplay w <- ListVal [ SymbolVal "display", w ]
|
||||
|
||||
pattern PApply :: Id -> [Syntax C] -> Syntax C
|
||||
pattern PApply f a <- ListVal ( SymbolVal f : a )
|
||||
|
||||
fetchRef :: forall m . MonadIO m => Ref -> FixerM m ()
|
||||
fetchRef r = do
|
||||
case r of
|
||||
RefRefLog ref -> do
|
||||
api <- asks _refLogAPI
|
||||
void $ liftIO $ oneSec $ void $ callService @RpcRefLogFetch api (fromRefLogKey ref)
|
||||
RefLWW ref -> do
|
||||
api <- asks _lwwAPI
|
||||
void $ liftIO $ oneSec $ void $ callService @RpcLWWRefFetch api ref
|
||||
|
||||
|
||||
getRefRpc :: forall m . MonadIO m => Ref -> FixerM m (Maybe HashRef)
|
||||
getRefRpc r = do
|
||||
case r of
|
||||
RefRefLog ref -> do
|
||||
api <- asks _refLogAPI
|
||||
liftIO (oneSec $ callService @RpcRefLogGet api (fromRefLogKey ref))
|
||||
>>= \case
|
||||
Right (Right x) -> pure x
|
||||
_ -> pure Nothing
|
||||
|
||||
RefLWW ref -> do
|
||||
api <- asks _lwwAPI
|
||||
liftIO (oneSec $ callService @RpcLWWRefGet api ref) >>= \case
|
||||
Right (Right x) -> pure (lwwValue <$> x)
|
||||
_ -> pure Nothing
|
||||
|
||||
subscribeRef :: forall m . MonadIO m => Integer -> Ref -> FixerM m ()
|
||||
subscribeRef n r = do
|
||||
debug $ "subscribeRef" <+> pretty n <+> pretty r
|
||||
let (puk,t) = case r of
|
||||
RefRefLog k -> (coerce k, "reflog")
|
||||
RefLWW k -> (coerce k, "lwwref")
|
||||
|
||||
let minutes = fromIntegral $ max 1 (n `div` 60)
|
||||
|
||||
api <- asks _peerAPI
|
||||
void $ liftIO $ oneSec $ callService @RpcPollAdd api (puk, t, minutes)
|
||||
|
||||
asRef :: Id -> AnyPolledRef -> Maybe Ref
|
||||
asRef t r = case t of
|
||||
"lwwref" -> Just $ RefLWW (coerce r)
|
||||
"reflog" -> Just $ RefRefLog (coerce r)
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
runWatcher :: forall m . MonadUnliftIO m => Watcher -> Ref -> HashRef -> FixerM m ()
|
||||
runWatcher (Watcher code) ref new = do
|
||||
debug $ yellow "CHANGED" <+> pretty ref <+> pretty new
|
||||
|
||||
sto <- asks _sto
|
||||
|
||||
newCode <- flip transformBiM code $ \case
|
||||
PApply "lwwref:get-hbs2-git-reflog" _ -> do
|
||||
v <- case ref of
|
||||
RefLWW k -> readLWWBlock sto k
|
||||
_ -> pure Nothing
|
||||
|
||||
-- FIXME: wrappers-for-syntax-ctors
|
||||
let vv = maybe1 v (List (noContext @C) mempty) $
|
||||
\(_, LWWBlockData{..}) ->
|
||||
List (noContext @C) [ Symbol (noContext @C) "reflog"
|
||||
, Literal (noContext @C)
|
||||
(mkLit @Text (fromString $ show $ pretty (AsBase58 lwwRefLogPubKey)))
|
||||
]
|
||||
pure vv
|
||||
|
||||
w -> pure w
|
||||
|
||||
debug (pretty newCode)
|
||||
runConfig newCode
|
||||
|
||||
|
||||
|
||||
display :: forall m . MonadUnliftIO m => Syntax C -> FixerM m ()
|
||||
display what = do
|
||||
case what of
|
||||
LitStrVal s -> notice (pretty s)
|
||||
ast -> notice (pretty ast)
|
||||
|
||||
list_ :: [Syntax C] -> Syntax C
|
||||
list_ = List (noContext @C)
|
||||
|
||||
symbol_ :: Id -> Syntax C
|
||||
symbol_ = Symbol (noContext @C)
|
||||
|
||||
str_ :: Text -> Syntax C
|
||||
str_ s = Literal (noContext @C) (LitStr s)
|
||||
|
||||
int_ :: Integer -> Syntax C
|
||||
int_ s = Literal (noContext @C) (LitInt s)
|
||||
|
||||
bool_ :: Bool -> Syntax C
|
||||
bool_ s = Literal (noContext @C) (LitBool s)
|
||||
|
||||
-- FIXME: to-suckless-conf
|
||||
class AsString s where
|
||||
asString :: s -> String
|
||||
|
||||
instance AsString Literal where
|
||||
asString (LitStr s) = Text.unpack s
|
||||
asString other = show $ pretty other
|
||||
|
||||
instance AsString (Syntax c) where
|
||||
asString (Literal _ x) = asString x
|
||||
asString x = show $ pretty x
|
||||
|
||||
data RunOpts =
|
||||
RunCWD FilePath
|
||||
|
||||
instance Pretty RunOpts where
|
||||
pretty = \case
|
||||
RunCWD f -> parens ("cwd" <+> pretty f)
|
||||
|
||||
eval :: forall m . MonadUnliftIO m => Syntax C -> FixerM m (Syntax C)
|
||||
eval = eval'
|
||||
-- debug $ "EVAL" <+> pretty syn <+> pretty r
|
||||
-- pure r
|
||||
|
||||
eval' :: forall m . MonadUnliftIO m => Syntax C -> FixerM m (Syntax C)
|
||||
eval' syn = do
|
||||
|
||||
case syn of
|
||||
|
||||
x@(Literal{}) -> pure x
|
||||
|
||||
(SymbolVal n) -> lookupLocal n
|
||||
|
||||
w@(PApply "list" code') -> do
|
||||
code <- mapM unquote code'
|
||||
pure $ list_ (symbol_ "list" : code)
|
||||
|
||||
PApply "local" [SymbolVal n, what] -> do
|
||||
bindLocal n =<< eval what
|
||||
pure nil
|
||||
|
||||
PApply "eval" [e] -> do
|
||||
eval e >>= \case
|
||||
(ListVal ( SymbolVal "list" : es ) ) -> do
|
||||
lastDef nil <$> mapM eval es
|
||||
|
||||
_ -> pure nil
|
||||
|
||||
PApply "listen" (what' : code) -> do
|
||||
what <- eval what'
|
||||
case what of
|
||||
PPolledRef "reflog" ref -> do
|
||||
setReflogListener (coerce ref) =<< mapM unquote code
|
||||
|
||||
PPolledRef tp r -> do
|
||||
warn $ yellow "not supported listener type" <+> pretty tp
|
||||
|
||||
_ -> pure ()
|
||||
|
||||
pure nil
|
||||
|
||||
PApply "watch" (p' : what' : watcher') -> do
|
||||
p <- eval p'
|
||||
what <- eval what'
|
||||
watcher <- mapM unquote watcher'
|
||||
|
||||
case (p, what) of
|
||||
(LitIntVal n, PPolledRef tp ref) -> do
|
||||
|
||||
let re = asRef tp ref
|
||||
|
||||
forM_ re (subscribeRef n)
|
||||
void $ async (pause @'Seconds 5 >> forM_ re fetchRef)
|
||||
|
||||
void $ runMaybeT do
|
||||
|
||||
-- FIXME: more-diagnostics
|
||||
pref <- toMPlus $ case tp of
|
||||
"lwwref" -> Just $ PolledRef (RefLWW (coerce ref), fromIntegral n)
|
||||
"reflog" -> Just $ PolledRef (RefRefLog (coerce ref), fromIntegral n)
|
||||
_ -> Nothing
|
||||
|
||||
debug $ blue "watch" <+> pretty n <+> pretty tp <+> pretty ref
|
||||
w <- asks _watchers
|
||||
atomically $ modifyTVar w (HM.insert pref (Watcher watcher))
|
||||
|
||||
_ -> pure ()
|
||||
|
||||
pure nil
|
||||
|
||||
PApply "on-start" wtf -> do
|
||||
|
||||
rn <- asks _runNum
|
||||
rnn <- atomically do
|
||||
x <- readTVar rn
|
||||
modifyTVar rn succ
|
||||
pure x
|
||||
|
||||
when (rnn == 0) do
|
||||
mapM_ eval wtf
|
||||
|
||||
pure nil
|
||||
|
||||
PApply fn args' -> do
|
||||
args <- mapM eval args'
|
||||
case fn of
|
||||
|
||||
"reflog" -> do
|
||||
pure $ list_ (symbol_ "reflog" : args)
|
||||
|
||||
"lwwref" -> do
|
||||
pure $ list_ (symbol_ "lwwref" : args)
|
||||
|
||||
"watch-config" -> do
|
||||
case headDef (int_ 30) args of
|
||||
LitIntVal n -> do
|
||||
debug $ "watch-config" <+> pretty n
|
||||
asks _configPoll >>= atomically . flip writeTVar (fromIntegral n)
|
||||
_ -> do
|
||||
pure ()
|
||||
|
||||
pure nil
|
||||
|
||||
"debug" -> do
|
||||
let onOff = headDef (bool_ False) args
|
||||
case onOff of
|
||||
LitBoolVal True -> do
|
||||
setLogging @DEBUG debugPrefix
|
||||
_ -> do
|
||||
setLoggingOff @DEBUG
|
||||
|
||||
pure nil
|
||||
|
||||
"string-append" -> do
|
||||
pieces <- for args $ \case
|
||||
LitStrVal s -> pure s
|
||||
other -> pure (Text.pack $ show $ pretty other)
|
||||
|
||||
pure $ str_ $ mconcat pieces
|
||||
|
||||
"display" -> do
|
||||
first <- headDef nil <$> mapM eval args
|
||||
case first of
|
||||
LitStrVal s -> notice (pretty s)
|
||||
ast -> notice (pretty ast)
|
||||
|
||||
pure nil
|
||||
|
||||
"getenv" -> do
|
||||
let name = asString $ headDef nil args
|
||||
liftIO $ Env.lookupEnv name
|
||||
>>= \case
|
||||
Nothing -> pure nil
|
||||
Just s -> pure $ str_ (fromString s)
|
||||
|
||||
"mkdir" -> do
|
||||
debug $ "mkdir" <+> pretty args
|
||||
mapM_ mkdir [ Text.unpack s | (LitStrVal s) <- args ]
|
||||
pure nil
|
||||
|
||||
"exit" -> do
|
||||
case headDef (int_ 0) args of
|
||||
LitIntVal 0 -> liftIO Exit.exitSuccess
|
||||
LitIntVal w -> liftIO $ Exit.exitWith (ExitFailure $ fromIntegral w)
|
||||
_ -> liftIO Exit.exitFailure
|
||||
|
||||
pure nil
|
||||
|
||||
"run" -> do
|
||||
debug $ red "RUN-ARGS" <+> pretty args
|
||||
(o,cargs) <- case args of
|
||||
(ListVal (SymbolVal "list" : SymbolVal "opts" : opts) : rest) -> do
|
||||
let pairs = [ (opt, e) | ListVal [SymbolVal opt, e] <- opts ]
|
||||
oo <- for pairs $ \(o, e) -> (o,) <$> eval e
|
||||
let cwd = lastMay [ RunCWD (Text.unpack f )
|
||||
| ("cwd", LitStrVal f) <- oo
|
||||
]
|
||||
pure (maybeToList cwd, rest)
|
||||
|
||||
rest -> do
|
||||
pure (mempty, rest)
|
||||
|
||||
let what = unwords $ [Text.unpack s | LitStrVal s <- cargs]
|
||||
|
||||
let cwd = case headMay [ p | c@(RunCWD p) <- o ] of
|
||||
Just c -> setWorkingDir c
|
||||
_ -> id
|
||||
|
||||
debug $ red "RUN" <+> pretty what <+> pretty o
|
||||
|
||||
let job = void $ runProcess_ (shell what & cwd)
|
||||
pip <- asks _pipeline
|
||||
atomically $ writeTQueue pip job
|
||||
|
||||
pure nil
|
||||
|
||||
_ -> pure nil
|
||||
|
||||
|
||||
_ -> pure nil
|
||||
|
||||
|
||||
unquote :: forall code m . (code ~ Syntax C, MonadUnliftIO m) => code -> FixerM m code
|
||||
unquote code = flip transformBiM code $ \case
|
||||
x@(ListVal [SymbolVal "unquoted", rest] :: Syntax C) -> do
|
||||
eval rest
|
||||
|
||||
x -> pure x
|
||||
|
||||
setReflogListener :: forall m . MonadUnliftIO m => RRefLog -> [Syntax C] -> FixerM m ()
|
||||
setReflogListener reflog code = do
|
||||
debug $ green "setReflogListener" <+> pretty reflog <> line <> pretty code
|
||||
|
||||
dudes <- asks _listeners
|
||||
|
||||
a <- atomically do
|
||||
x <- readTVar dudes <&> HM.lookup reflog
|
||||
modifyTVar dudes (HM.delete reflog)
|
||||
pure x
|
||||
|
||||
maybe1 a none cancel
|
||||
|
||||
sink <- asks _refLogSink
|
||||
|
||||
debug $ "subscribe to" <+> pretty reflog
|
||||
|
||||
new <- async do
|
||||
cache <- liftIO $ Cache.newCache (Just (toTimeSpec (TimeoutSec 10)))
|
||||
|
||||
runNotifySink sink (RefLogNotifyKey reflog) $ \(RefLogUpdateNotifyData _ h) -> do
|
||||
debug $ "Got notification" <+> pretty reflog <+> pretty h
|
||||
here <- liftIO (Cache.lookup cache (reflog, h)) <&> isJust
|
||||
unless here do
|
||||
liftIO $ Cache.insert cache (reflog,h) ()
|
||||
runConfig code
|
||||
|
||||
atomically $ modifyTVar dudes (HM.insert reflog new)
|
||||
|
||||
bindLocal :: forall m . MonadUnliftIO m => Id -> Syntax C -> FixerM m ()
|
||||
bindLocal l e = do
|
||||
-- debug $ "bindLocal" <+> pretty l
|
||||
asks _locals >>= atomically . flip modifyTVar (HM.insert l e)
|
||||
|
||||
lookupLocal :: forall m . MonadUnliftIO m => Id ->FixerM m (Syntax C)
|
||||
lookupLocal name = do
|
||||
-- debug $ "lookupLocal" <+> pretty name
|
||||
asks _locals >>= readTVarIO <&> fromMaybe nil . HM.lookup name
|
||||
|
||||
runConfig :: forall m . MonadUnliftIO m => Config -> FixerM m ()
|
||||
runConfig conf = do
|
||||
debug $ green "runConfig"
|
||||
bindLocal "off" (bool_ False)
|
||||
bindLocal "on" (bool_ True)
|
||||
|
||||
mapM_ eval conf
|
||||
|
||||
updateFromConfig :: MonadUnliftIO m => Config -> FixerM m ()
|
||||
updateFromConfig conf = do
|
||||
asks _config >>= atomically . flip writeTVar conf
|
||||
runConfig conf
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
runMe =<< customExecParser (prefs showHelpOnError)
|
||||
( info (helper <*> opts)
|
||||
( fullDesc
|
||||
<> header "hbs2-fixer"
|
||||
<> progDesc "Intermediary between hbs2-peer and external applications. Listen events / do stuff"
|
||||
))
|
||||
|
||||
where
|
||||
opts = optional $ strOption (short 'c' <> long "config" <> metavar "FILE" <> help "Specify configuration file")
|
||||
|
||||
runMe opt = withApp opt mainLoop
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
;; hbs2-fixer config example
|
||||
|
||||
(local home (getenv "HOME"))
|
||||
|
||||
(local root (string-append home "/.local/share/hbs2-git-repos/0.24.1"))
|
||||
|
||||
(local hbs2-repo "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" )
|
||||
(local hbs2-repo-path (string-append root "/" hbs2-repo))
|
||||
|
||||
(watch-config 30)
|
||||
|
||||
(debug off)
|
||||
|
||||
(display (string-append "repo1" " " hbs2-repo-path))
|
||||
|
||||
(eval (list (display "OKAY11 FROM EVAL")))
|
||||
|
||||
(on-start
|
||||
(display (string-append "on-start" " " "mkdir" " " hbs2-repo-path))
|
||||
|
||||
(mkdir hbs2-repo-path)
|
||||
|
||||
(run (string-append "git init --bare " hbs2-repo-path))
|
||||
(display update-hbs2-repo)
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo))
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git gc" ) )
|
||||
)
|
||||
|
||||
(watch 60 (lwwref "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP")
|
||||
(run-config
|
||||
(watch 300 (lwwref:get-hbs2-git-reflog)
|
||||
(display "GIT REFLOG CHANGED BY WATCH")
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo ))
|
||||
|
||||
(display (string-append "Updated " hbs2-repo " OK"))
|
||||
|
||||
)
|
||||
|
||||
(listen (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(display "GIT REFLOG CHANGED BY LISTENER")
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo ))
|
||||
|
||||
(display (string-append "Updated " hbs2-repo " OK"))
|
||||
)
|
||||
|
||||
)
|
||||
(display (string-append "Updated " hbs2-repo))
|
||||
)
|
||||
|
||||
; (watch 30 (lwwref "Byc3XUeSbJBXVFueumkNkVJMPHbGoUdxYEJBgzJPf8io")
|
||||
; (run "./on-my-ref4.sh")
|
||||
; )
|
||||
|
||||
; (watch 30 (lwwref "DTmSb3Au7apDTMctQn6yqs9GJ8mFW7YQXzgVqZpmkTtf")
|
||||
; (run "./on-my-ref4.sh")
|
||||
; )
|
||||
|
||||
; (watch 30 (reflog "BKtvRLispCM9UuQqHaNxu4SEUzpQNQ3PeRNknecKGPZ6")
|
||||
; (run "./on-my-ref4.sh")
|
||||
; )
|
||||
|
||||
; (display "JOPAKITA 111")
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
(display (getenv 1234))
|
||||
|
||||
(display (getenv "HOME"))
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
(local home (getenv "HOME"))
|
||||
|
||||
(local root (string-append home "/.local/share/hbs2-git-repos/0.24.1"))
|
||||
|
||||
(local hbs2-repo "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" )
|
||||
(local hbs2-repo-path (string-append root "/" hbs2-repo))
|
||||
|
||||
(display root)
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
;; hbs2-fixer config example
|
||||
|
||||
; (debug off)
|
||||
|
||||
(watch-config 30)
|
||||
|
||||
(local home (getenv "HOME"))
|
||||
|
||||
(local root (string-append home "/.local/share/hbs2-git-repos/0.24.1"))
|
||||
|
||||
(local hbs2-repo "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" )
|
||||
(local hbs2-repo-path (string-append root "/" hbs2-repo))
|
||||
|
||||
|
||||
(local myref "BKtvRLispCM9UuQqHaNxu4SEUzpQNQ3PeRNknecKGPZ6" )
|
||||
|
||||
(listen (reflog myref)
|
||||
(display (string-append "HELLO FROM REFLOG " (unquoted myref)))
|
||||
)
|
||||
|
||||
(listen (lwwref myref)
|
||||
(display "WON'T HAPPEN")
|
||||
)
|
||||
|
||||
(display "FUUBAR!")
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
(local code (list (display "HELLO")))
|
||||
|
||||
(eval code)
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
(watch 30 (lwwref "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP")
|
||||
(display "PREVED")
|
||||
)
|
|
@ -1,129 +0,0 @@
|
|||
cabal-version: 3.0
|
||||
name: hbs2-fixer
|
||||
version: 0.25.0.1
|
||||
-- synopsis:
|
||||
-- description:
|
||||
license: BSD-3-Clause
|
||||
license-file: LICENSE
|
||||
author: Dmitry Zuikov
|
||||
maintainer: dzuikov@gmail.com
|
||||
-- copyright:
|
||||
category: Development
|
||||
build-type: Simple
|
||||
extra-doc-files: CHANGELOG.md
|
||||
-- extra-source-files:
|
||||
|
||||
common shared-properties
|
||||
ghc-options:
|
||||
-Wall
|
||||
-Wno-type-defaults
|
||||
-fprint-potential-instances
|
||||
-- -fno-warn-unused-matches
|
||||
-- -fno-warn-unused-do-bind
|
||||
-- -Werror=missing-methods
|
||||
-- -Werror=incomplete-patterns
|
||||
-- -fno-warn-unused-binds
|
||||
|
||||
|
||||
default-language: Haskell2010
|
||||
|
||||
default-extensions:
|
||||
ApplicativeDo
|
||||
, BangPatterns
|
||||
, BlockArguments
|
||||
, ConstraintKinds
|
||||
, DataKinds
|
||||
, DeriveDataTypeable
|
||||
, DeriveGeneric
|
||||
, DerivingStrategies
|
||||
, DerivingVia
|
||||
, ExtendedDefaultRules
|
||||
, FlexibleContexts
|
||||
, FlexibleInstances
|
||||
, GADTs
|
||||
, GeneralizedNewtypeDeriving
|
||||
, ImportQualifiedPost
|
||||
, LambdaCase
|
||||
, MultiParamTypeClasses
|
||||
, OverloadedStrings
|
||||
, QuasiQuotes
|
||||
, RecordWildCards
|
||||
, ScopedTypeVariables
|
||||
, StandaloneDeriving
|
||||
, TupleSections
|
||||
, TypeApplications
|
||||
, TypeOperators
|
||||
, TypeFamilies
|
||||
, TemplateHaskell
|
||||
|
||||
|
||||
build-depends: hbs2-core, hbs2-peer, hbs2-git
|
||||
, attoparsec
|
||||
, aeson
|
||||
, async
|
||||
, base16-bytestring
|
||||
, bytestring
|
||||
, cache
|
||||
, containers
|
||||
, streaming
|
||||
, streaming-bytestring
|
||||
, streaming-commons
|
||||
, crypton
|
||||
, directory
|
||||
, exceptions
|
||||
, filelock
|
||||
, filepath
|
||||
, filepattern
|
||||
, generic-lens
|
||||
, hashable
|
||||
, http-conduit
|
||||
, interpolatedstring-perl6
|
||||
, memory
|
||||
, microlens-platform
|
||||
, mtl
|
||||
, prettyprinter
|
||||
, prettyprinter-ansi-terminal
|
||||
, random
|
||||
, resourcet
|
||||
, safe
|
||||
, saltine
|
||||
, serialise
|
||||
, split
|
||||
, sqlite-simple
|
||||
, stm
|
||||
, suckless-conf
|
||||
, temporary
|
||||
, text
|
||||
, time
|
||||
, timeit
|
||||
, transformers
|
||||
, typed-process
|
||||
, uniplate
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
, unordered-containers
|
||||
, wai-app-file-cgi
|
||||
, wai-extra
|
||||
|
||||
executable hbs2-fixer
|
||||
import: shared-properties
|
||||
main-is: Main.hs
|
||||
|
||||
ghc-options:
|
||||
-threaded
|
||||
-rtsopts
|
||||
"-with-rtsopts=-N4 -A64m -AL256m -I0"
|
||||
|
||||
other-modules:
|
||||
|
||||
-- other-extensions:
|
||||
build-depends:
|
||||
base, hbs2-core, hbs2-peer
|
||||
, optparse-applicative
|
||||
, unliftio
|
||||
|
||||
hs-source-dirs: app
|
||||
default-language: GHC2021
|
||||
|
||||
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
; vim: set filetype=scheme :
|
||||
;; hbs2-fixer config example
|
||||
|
||||
|
||||
(local root "/var/www/git")
|
||||
|
||||
(local hbs2-ref (lwwref "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP"))
|
||||
(local hbs2-repo "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" )
|
||||
(local hbs2-repo-path (string-append root "/" hbs2-repo))
|
||||
|
||||
|
||||
(local suckless-ref (lwwref "JAuk1UJzZfbDGKVazSQU5yYQ3NGfk4gVeZzBCduf5TgQ"))
|
||||
(local suckless-repo "JAuk1UJzZfbDGKVazSQU5yYQ3NGfk4gVeZzBCduf5TgQ" )
|
||||
(local suckless-repo-path (string-append root "/" suckless-repo))
|
||||
|
||||
(local fixme-ref (lwwref "Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr"))
|
||||
(local fixme-repo "Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr" )
|
||||
(local fixme-repo-path (string-append root "/" fixme-repo))
|
||||
|
||||
(local dbpipe-ref (lwwref "5xrwbTzzweS9yeJQnrrUY9gQJfhJf84pbyHhF2MMmSft"))
|
||||
(local dbpipe-repo "5xrwbTzzweS9yeJQnrrUY9gQJfhJf84pbyHhF2MMmSft" )
|
||||
(local dbpipe-repo-path (string-append root "/" dbpipe-repo))
|
||||
|
||||
(watch-config 30)
|
||||
|
||||
(debug on)
|
||||
|
||||
(display (string-append "repo1" " " hbs2-repo-path))
|
||||
|
||||
(on-start
|
||||
(display (string-append "on-start" " " "mkdir" " " hbs2-repo-path))
|
||||
|
||||
(mkdir hbs2-repo-path)
|
||||
|
||||
(run (string-append "git init --bare " hbs2-repo-path))
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo))
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git gc" ) )
|
||||
|
||||
|
||||
(mkdir suckless-repo-path)
|
||||
|
||||
(run (string-append "git init --bare " suckless-repo-path))
|
||||
|
||||
(run (list opts (cwd suckless-repo-path))
|
||||
(string-append "git hbs2 import" " " suckless-repo))
|
||||
|
||||
(run (list opts (cwd suckless-repo-path))
|
||||
(string-append "git gc" ) )
|
||||
|
||||
(mkdir fixme-repo-path)
|
||||
|
||||
(run (string-append "git init --bare " fixme-repo-path))
|
||||
|
||||
(run (list opts (cwd fixme-repo-path))
|
||||
(string-append "git hbs2 import" " " fixme-repo))
|
||||
|
||||
(run (list opts (cwd fixme-repo-path))
|
||||
(string-append "git gc" ) )
|
||||
|
||||
|
||||
(mkdir dbpipe-repo-path)
|
||||
|
||||
(run (string-append "git init --bare " dbpipe-repo-path))
|
||||
|
||||
(run (list opts (cwd dbpipe-repo-path))
|
||||
(string-append "git hbs2 import" " " dbpipe-repo))
|
||||
|
||||
(run (list opts (cwd dbpipe-repo-path))
|
||||
(string-append "git gc" ) )
|
||||
|
||||
)
|
||||
|
||||
(watch 60 hbs2-ref
|
||||
(display (string-append "hbs2-repo" " " hbs2-ref))
|
||||
|
||||
(display (string-append "hbs2-repo" " " hbs2-ref))
|
||||
(run-config
|
||||
|
||||
(display (string-append "REF" (lwwref:get-hbs2-git-reflog)))
|
||||
|
||||
(watch 300 (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo ))
|
||||
|
||||
(display (string-append "Updated " hbs2-repo " OK"))
|
||||
|
||||
)
|
||||
|
||||
(listen (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(display "subscribed hbs2")
|
||||
|
||||
(run (list opts (cwd hbs2-repo-path))
|
||||
(string-append "git hbs2 import" " " hbs2-repo ))
|
||||
|
||||
(display (string-append "Updated " hbs2-repo " OK")))
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
;; fixme
|
||||
|
||||
(watch 60 fixme-ref
|
||||
(display (string-append "fixme-repo" " " fixme-ref))
|
||||
|
||||
(display (string-append "fixme-repo" " " fixme-ref))
|
||||
(run-config
|
||||
|
||||
(display (string-append "REF" (lwwref:get-hbs2-git-reflog)))
|
||||
|
||||
(watch 300 (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(run (list opts (cwd fixme-repo-path))
|
||||
(string-append "git hbs2 import" " " fixme-repo ))
|
||||
|
||||
(display (string-append "Updated " fixme-repo " OK"))
|
||||
|
||||
)
|
||||
|
||||
(listen (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(display "subscribed fixme")
|
||||
|
||||
(run (list opts (cwd fixme-repo-path))
|
||||
(string-append "git hbs2 import" " " fixme-repo ))
|
||||
|
||||
(display (string-append "Updated " fixme-repo " OK")))
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
;; suckless
|
||||
|
||||
(watch 60 suckless-ref
|
||||
(display (string-append "suckless-repo" " " fixme-ref))
|
||||
|
||||
(display (string-append "suckless-repo" " " fixme-ref))
|
||||
(run-config
|
||||
|
||||
(display (string-append "REF" (lwwref:get-hbs2-git-reflog)))
|
||||
|
||||
(watch 300 (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(run (list opts (cwd suckless-repo-path))
|
||||
(string-append "git hbs2 import" " " suckless-repo ))
|
||||
|
||||
(display (string-append "Updated " suckless-repo " OK"))
|
||||
|
||||
)
|
||||
|
||||
(listen (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(display "subscribed suckless")
|
||||
|
||||
(run (list opts (cwd suckless-repo-path))
|
||||
(string-append "git hbs2 import" " " suckless-repo ))
|
||||
|
||||
(display (string-append "Updated " suckless-repo " OK")))
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
;; dbpipe
|
||||
|
||||
(watch 60 dbpipe-ref
|
||||
(display (string-append "dbpipe-repo" " " fixme-ref))
|
||||
|
||||
(display (string-append "dbpipe-repo" " " fixme-ref))
|
||||
(run-config
|
||||
|
||||
(display (string-append "REF" (lwwref:get-hbs2-git-reflog)))
|
||||
|
||||
(watch 300 (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(run (list opts (cwd dbpipe-repo-path))
|
||||
(string-append "git hbs2 import" " " dbpipe-repo ))
|
||||
|
||||
(display (string-append "Updated " dbpipe-repo " OK"))
|
||||
|
||||
)
|
||||
|
||||
(listen (lwwref:get-hbs2-git-reflog)
|
||||
|
||||
(display "subscribed dbpipe")
|
||||
|
||||
(run (list opts (cwd dbpipe-repo-path))
|
||||
(string-append "git hbs2 import" " " dbpipe-repo ))
|
||||
|
||||
(display (string-append "Updated " dbpipe-repo " OK")))
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1,794 +0,0 @@
|
|||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# Language UndecidableInstances #-}
|
||||
{-# Language AllowAmbiguousTypes #-}
|
||||
module Main where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
|
||||
import HBS2.Net.Messaging.Unix
|
||||
import HBS2.Net.Proto
|
||||
import HBS2.Net.Proto.Service
|
||||
|
||||
import HBS2.System.Dir
|
||||
import HBS2.OrDie
|
||||
import HBS2.Polling
|
||||
|
||||
import HBS2.Actors.Peer
|
||||
import HBS2.Peer.RPC.API.Storage
|
||||
import HBS2.Peer.RPC.Client.StorageClient
|
||||
|
||||
import HBS2.Git.Web.Assets
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.State.Index
|
||||
import HBS2.Git.DashBoard.State.Commits
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.Fixme
|
||||
import HBS2.Git.DashBoard.Manifest
|
||||
import HBS2.Git.Web.Html.Root
|
||||
import HBS2.Git.Web.Html.Issue
|
||||
import HBS2.Git.Web.Html.Repo
|
||||
import HBS2.Git.Web.Html.Fixme
|
||||
import HBS2.Peer.CLI.Detect
|
||||
|
||||
import DBPipe.SQLite
|
||||
|
||||
import Data.Config.Suckless.Script
|
||||
|
||||
import Lucid (renderTextT,HtmlT(..),toHtml)
|
||||
import Data.Either
|
||||
import Data.Text qualified as Text
|
||||
import Data.Text.Lazy qualified as LT
|
||||
import Data.ByteString.Lazy qualified as LBS
|
||||
import Data.ByteString.Lazy (ByteString)
|
||||
import Network.HTTP.Types.Status
|
||||
import Network.Wai.Middleware.Static hiding ((<|>))
|
||||
import Network.Wai.Middleware.StaticEmbedded as E
|
||||
import Network.Wai.Middleware.RequestLogger
|
||||
import Web.Scotty.Trans as Scotty
|
||||
import Control.Monad.Except
|
||||
import System.Random
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Control.Concurrent.STM (flushTQueue)
|
||||
import System.FilePath
|
||||
import System.Process.Typed
|
||||
import System.Directory (XdgDirectory(..),getXdgDirectory)
|
||||
import Data.ByteString.Lazy.Char8 qualified as LBS8
|
||||
import System.Environment
|
||||
import System.Exit
|
||||
import System.IO.Temp
|
||||
|
||||
{- HLINT ignore "Eta reduce" -}
|
||||
{- HLINT ignore "Functor law" -}
|
||||
|
||||
getRPC :: Monad m => HasConf m => m (Maybe FilePath)
|
||||
getRPC = pure Nothing
|
||||
|
||||
data CallRPC
|
||||
data PingRPC
|
||||
data IndexNowRPC
|
||||
|
||||
type MyRPC = '[ PingRPC, IndexNowRPC, CallRPC ]
|
||||
|
||||
instance HasProtocol UNIX (ServiceProto MyRPC UNIX) where
|
||||
type instance ProtocolId (ServiceProto MyRPC UNIX) = 0xFAFABEBE
|
||||
type instance Encoded UNIX = ByteString
|
||||
decode = either (const Nothing) Just . deserialiseOrFail
|
||||
encode = serialise
|
||||
|
||||
type instance Input CallRPC = String
|
||||
type instance Output CallRPC = String
|
||||
|
||||
type instance Input PingRPC = ()
|
||||
type instance Output PingRPC = String
|
||||
|
||||
type instance Input IndexNowRPC = ()
|
||||
type instance Output IndexNowRPC = ()
|
||||
|
||||
class HasDashBoardEnv m where
|
||||
getDashBoardEnv :: m DashBoardEnv
|
||||
|
||||
instance (MonadIO m) => HandleMethod m CallRPC where
|
||||
handleMethod n = do
|
||||
debug $ "RPC CALL" <+> pretty n
|
||||
pure ""
|
||||
|
||||
instance (MonadIO m, HasDashBoardEnv m) => HandleMethod m PingRPC where
|
||||
handleMethod _ = do
|
||||
debug $ "RPC PING"
|
||||
pure "pong"
|
||||
|
||||
instance (DashBoardPerks m, HasDashBoardEnv m) => HandleMethod m IndexNowRPC where
|
||||
handleMethod _ = do
|
||||
e <- getDashBoardEnv
|
||||
debug $ "rpc: index:now"
|
||||
withDashBoardEnv e $ addJob (liftIO $ withDashBoardEnv e updateIndex)
|
||||
|
||||
instance HasLimit (FromParams 'FixmeDomain [Param]) where
|
||||
-- TODO: optimal-page-size
|
||||
limit (FromParams p) = Just limits
|
||||
where
|
||||
pageSize = fromIntegral fixmePageSize
|
||||
page = fromMaybe 0 $ headMay [ readDef 0 (Text.unpack n) | ("$page", n) <- p ]
|
||||
offset = page
|
||||
limits = (fromIntegral offset, fromIntegral pageSize)
|
||||
|
||||
instance HasPredicate (FromParams 'FixmeDomain [Param]) where
|
||||
predicate (FromParams args) = do
|
||||
flip fix seed $ \next -> \case
|
||||
[] -> All
|
||||
( clause : rest ) -> And clause (next rest)
|
||||
|
||||
where
|
||||
seed = [ AttrLike a b | (a,b) <- args, a /= "$page" ]
|
||||
|
||||
readConfig :: DashBoardPerks m => m [Syntax C]
|
||||
readConfig = do
|
||||
|
||||
xdgConf <- liftIO $ getXdgDirectory XdgConfig hbs2_git_dashboard
|
||||
|
||||
let confPath = xdgConf
|
||||
let confFile = confPath </> "config"
|
||||
|
||||
touch confFile
|
||||
|
||||
runExceptT (liftIO $ readFile confFile)
|
||||
<&> fromRight mempty
|
||||
<&> parseTop
|
||||
<&> fromRight mempty
|
||||
|
||||
runDashBoardM :: DashBoardPerks m => DashBoardM m a -> m a
|
||||
runDashBoardM m = do
|
||||
|
||||
xdgData <- liftIO $ getXdgDirectory XdgData hbs2_git_dashboard
|
||||
|
||||
let dataDir = xdgData
|
||||
|
||||
-- FIXME: unix-socket-from-config
|
||||
soname <- detectRPC `orDie` "hbs2-peer rpc not found"
|
||||
|
||||
let errorPrefix = toStderr . logPrefix "[error] "
|
||||
let warnPrefix = toStderr . logPrefix "[warn] "
|
||||
let noticePrefix = toStderr . logPrefix ""
|
||||
let debugPrefix = toStderr . logPrefix "[debug] "
|
||||
|
||||
setLogging @INFO defLog
|
||||
setLogging @ERROR errorPrefix
|
||||
setLogging @DEBUG debugPrefix
|
||||
setLogging @WARN warnPrefix
|
||||
setLogging @NOTICE noticePrefix
|
||||
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
|
||||
client <- liftIO $ race (pause @'Seconds 1) (newMessagingUnix False 1.0 soname)
|
||||
>>= orThrowUser ("can't connect to" <+> pretty soname)
|
||||
|
||||
void $ ContT $ withAsync $ runMessagingUnix client
|
||||
|
||||
peerAPI <- makeServiceCaller @PeerAPI (fromString soname)
|
||||
refLogAPI <- makeServiceCaller @RefLogAPI (fromString soname)
|
||||
refChanAPI <- makeServiceCaller @RefChanAPI (fromString soname)
|
||||
storageAPI <- makeServiceCaller @StorageAPI (fromString soname)
|
||||
lwwAPI <- makeServiceCaller @LWWRefAPI (fromString soname)
|
||||
|
||||
let sto = AnyStorage (StorageClient storageAPI)
|
||||
|
||||
let endpoints = [ Endpoint @UNIX peerAPI
|
||||
, Endpoint @UNIX refLogAPI
|
||||
, Endpoint @UNIX refChanAPI
|
||||
, Endpoint @UNIX lwwAPI
|
||||
, Endpoint @UNIX storageAPI
|
||||
]
|
||||
|
||||
void $ ContT $ withAsync $ liftIO $ runReaderT (runServiceClientMulti endpoints) client
|
||||
|
||||
|
||||
env <- newDashBoardEnv
|
||||
dataDir
|
||||
peerAPI
|
||||
refLogAPI
|
||||
refChanAPI
|
||||
lwwAPI
|
||||
sto
|
||||
|
||||
lift $ withDashBoardEnv env do
|
||||
mkdir dataDir
|
||||
notice "evolving db"
|
||||
withState evolveDB
|
||||
|
||||
void $ ContT $ withAsync do
|
||||
fix \next -> do
|
||||
dbe' <- readTVarIO (_db env)
|
||||
case dbe' of
|
||||
Just dbe -> do
|
||||
notice $ green "Aquired database!"
|
||||
runPipe dbe
|
||||
forever do
|
||||
pause @'Seconds 30
|
||||
|
||||
Nothing -> do
|
||||
pause @'Seconds 5
|
||||
next
|
||||
|
||||
replicateM_ 2 do
|
||||
ContT $ withAsync do
|
||||
q <- withDashBoardEnv env $ asks _pipeline
|
||||
forever do
|
||||
liftIO (atomically $ readTQueue q) & liftIO . join
|
||||
|
||||
lift $ withDashBoardEnv env m
|
||||
`finally` do
|
||||
setLoggingOff @DEBUG
|
||||
setLoggingOff @INFO
|
||||
setLoggingOff @ERROR
|
||||
setLoggingOff @WARN
|
||||
setLoggingOff @NOTICE
|
||||
|
||||
|
||||
data WebOptions =
|
||||
WebOptions
|
||||
{ _assetsOverride :: Maybe FilePath
|
||||
}
|
||||
|
||||
orFall :: m r -> Maybe a -> ContT r m a
|
||||
orFall a mb = ContT $ maybe1 mb a
|
||||
|
||||
renderHtml :: forall m a . MonadIO m => HtmlT (ActionT m) a -> ActionT m ()
|
||||
renderHtml m = renderTextT m >>= html
|
||||
|
||||
runDashboardWeb :: WebOptions -> ScottyT (DashBoardM IO) ()
|
||||
runDashboardWeb WebOptions{..} = do
|
||||
middleware logStdout
|
||||
|
||||
case _assetsOverride of
|
||||
Nothing -> do
|
||||
middleware (E.static assetsDir)
|
||||
Just f -> do
|
||||
middleware $ staticPolicy (noDots >-> addBase f)
|
||||
|
||||
get (routePattern RepoListPage) do
|
||||
renderHtml dashboardRootPage
|
||||
|
||||
|
||||
get "/:lww" do
|
||||
lww <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
>>= orThrow (itemNotFound "repository key")
|
||||
|
||||
asksBaseUrl $ withBaseUrl $
|
||||
redirect (LT.fromStrict $ toBaseURL (RepoPage (CommitsTab Nothing) lww))
|
||||
|
||||
get (routePattern (RepoPage "tab" "lww")) do
|
||||
lww <- captureParam @String "lww" <&> fromStringMay
|
||||
>>= orThrow (itemNotFound "repository key")
|
||||
|
||||
tab <- captureParam @String "tab"
|
||||
<&> fromStringMay
|
||||
<&> fromMaybe (CommitsTab Nothing)
|
||||
|
||||
qp <- queryParams
|
||||
|
||||
renderHtml (repoPage tab lww qp)
|
||||
|
||||
get (routePattern (RepoManifest "lww")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
TopInfoBlock{..} <- lift $ getTopInfoBlock lww
|
||||
lift $ html (LT.fromStrict manifest)
|
||||
|
||||
get (routePattern (RepoRefs "lww")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
|
||||
-- setHeader "HX-Push-Url" [qc|/{show $ pretty lwws'}|]
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
lift $ renderHtml (repoRefs lww)
|
||||
|
||||
get (routePattern (RepoTree "lww" "co" "hash")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
hash' <- captureParam @String "hash" <&> fromStringMay @GitHash
|
||||
co' <- captureParam @String "co" <&> fromStringMay @GitHash
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
hash <- hash' & orFall (status status404)
|
||||
co <- co' & orFall (status status404)
|
||||
lift $ renderHtml (repoTree lww co hash)
|
||||
|
||||
get (routePattern (RepoBlob "lww" "co" "hash" "blob")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
hash' <- captureParam @String "hash" <&> fromStringMay @GitHash
|
||||
co' <- captureParam @String "co" <&> fromStringMay @GitHash
|
||||
blob' <- captureParam @String "blob" <&> fromStringMay @GitHash
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
hash <- hash' & orFall (status status404)
|
||||
co <- co' & orFall (status status404)
|
||||
blobHash <- blob' & orFall (status status404)
|
||||
|
||||
blobInfo <- lift (selectBlobInfo (BlobHash blobHash))
|
||||
>>= orFall (status status404)
|
||||
|
||||
lift $ renderHtml (repoBlob lww (TreeCommit co) (TreeTree hash) blobInfo)
|
||||
|
||||
get (routePattern (RepoSomeBlob "lww" "syntax" "blob")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
syn <- captureParamMaybe @Text "syntax" <&> fromMaybe "default"
|
||||
blob' <- captureParam @String "blob" <&> fromStringMay @GitHash
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
blob <- blob' & orFall (status status404)
|
||||
lift $ renderHtml (repoSomeBlob lww syn blob)
|
||||
|
||||
get (routePattern (RepoCommitDefault "lww" "hash")) (commitRoute RepoCommitSummary)
|
||||
get (routePattern (RepoCommitSummaryQ "lww" "hash")) (commitRoute RepoCommitSummary)
|
||||
get (routePattern (RepoCommitPatchQ "lww" "hash")) (commitRoute RepoCommitPatch)
|
||||
|
||||
get (routePattern (RepoForksHtmx "lww")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
lift $ renderHtml (repoForks lww)
|
||||
-- lift $ renderHtml (toHtml $ show $ pretty lww)
|
||||
|
||||
get (routePattern (IssuePage "lww" "fixme")) do
|
||||
|
||||
r <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
f <- captureParam @String "fixme" <&> fromStringMay @FixmeKey
|
||||
|
||||
debug $ blue "AAAA" <+> pretty r <+> pretty f
|
||||
|
||||
flip runContT pure do
|
||||
lww <- r & orFall (status status404)
|
||||
fme <- f & orFall (status status404)
|
||||
|
||||
lift $ renderHtml (issuePage (RepoLww lww) fme)
|
||||
|
||||
get (routePattern (RepoFixmeHtmx mempty "lww")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
p <- queryParams
|
||||
debug $ "FIXME: GET QUERY" <+> pretty p
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
lift $ renderHtml (repoFixme (FromParams @'FixmeDomain p) lww)
|
||||
|
||||
get (routePattern (RepoCommits "lww")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
|
||||
let pred = mempty & set commitPredOffset 0
|
||||
& set commitPredLimit 100
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
lift $ renderHtml (repoCommits lww (Right pred))
|
||||
|
||||
get (routePattern (RepoCommitsQ "lww" "off" "lim")) do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey 'HBS2Basic)
|
||||
off <- captureParam @Int "off"
|
||||
lim <- captureParam @Int "lim"
|
||||
|
||||
let pred = mempty & set commitPredOffset off
|
||||
& set commitPredLimit lim
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
lww <- lwws' & orFall (status status404)
|
||||
|
||||
-- FIXME: this
|
||||
referrer <- asksBaseUrl $ withBaseUrl $ lift (Scotty.header "Referer")
|
||||
>>= orFall (redirect $ LT.fromStrict $ toBaseURL (RepoPage (CommitsTab Nothing) lww))
|
||||
|
||||
lift $ renderHtml (repoCommits lww (Left pred))
|
||||
|
||||
-- "pages"
|
||||
|
||||
where
|
||||
commitRoute style = do
|
||||
lwws' <- captureParam @String "lww" <&> fromStringMay @(LWWRefKey HBS2Basic)
|
||||
co <- captureParam @String "hash" <&> fromStringMay @GitHash
|
||||
|
||||
referrer <- Scotty.header "Referer"
|
||||
debug $ yellow "COMMIT-REFERRER" <+> pretty referrer
|
||||
|
||||
flip runContT pure do
|
||||
lww <- lwws' & orFall (status status404)
|
||||
hash <- co & orFall (status status404)
|
||||
lift $ renderHtml (repoCommit style lww hash)
|
||||
|
||||
|
||||
runScotty :: DashBoardPerks m => DashBoardM m ()
|
||||
runScotty = do
|
||||
|
||||
env <- ask
|
||||
|
||||
notice "running config"
|
||||
conf <- readConfig
|
||||
|
||||
run theDict conf
|
||||
|
||||
pno <- getHttpPortNumber
|
||||
wo <- WebOptions <$> getDevAssets
|
||||
|
||||
flip runContT pure do
|
||||
void $ ContT $ withAsync updateIndexPeriodially
|
||||
void $ ContT $ withAsync runRPC
|
||||
scottyT pno (withDashBoardEnv env) (runDashboardWeb wo)
|
||||
|
||||
|
||||
data RPCEnv = RPCEnv
|
||||
{ rpcMessaging :: MessagingUnix
|
||||
, dashBoardEnv :: DashBoardEnv
|
||||
}
|
||||
|
||||
newtype RunRPCM m a = RunRPCM { fromRunRPC :: ReaderT RPCEnv m a }
|
||||
deriving newtype ( Applicative
|
||||
, Functor
|
||||
, Monad
|
||||
, MonadIO
|
||||
, MonadUnliftIO
|
||||
, MonadTrans
|
||||
, MonadReader RPCEnv
|
||||
)
|
||||
runRPCMonad :: DashBoardEnv -> MessagingUnix -> RunRPCM m a -> m a
|
||||
runRPCMonad env s m = runReaderT (fromRunRPC m) (RPCEnv s env)
|
||||
|
||||
instance HasFabriq UNIX (RunRPCM IO) where
|
||||
getFabriq = asks (Fabriq . rpcMessaging)
|
||||
|
||||
instance HasOwnPeer UNIX (RunRPCM IO) where
|
||||
ownPeer = asks ( msgUnixSelf . rpcMessaging)
|
||||
|
||||
instance HasDashBoardEnv (ResponseM UNIX (RunRPCM IO)) where
|
||||
getDashBoardEnv = lift $ asks dashBoardEnv
|
||||
|
||||
runRPC :: DashBoardPerks m => DashBoardM m ()
|
||||
runRPC = do
|
||||
debug $ green "runRPC loop"
|
||||
|
||||
env <- ask
|
||||
|
||||
liftIO $ flip runContT pure do
|
||||
|
||||
soname <- ContT $ bracket (liftIO $ emptySystemTempFile "hbs2-git-dashboard-socket") rm
|
||||
|
||||
liftIO $ withDashBoardEnv env do
|
||||
setRPCSocket soname
|
||||
|
||||
void $ ContT $ bracket (pure soname) (\_ -> withDashBoardEnv env $ delRPCSocket)
|
||||
|
||||
notice $ green "rpc-socket" <+> pretty soname
|
||||
|
||||
server <- newMessagingUnix True 1.0 soname
|
||||
|
||||
m1 <- ContT $ withAsync (runMessagingUnix server)
|
||||
|
||||
p1 <- ContT $ withAsync $ runRPCMonad env server do
|
||||
runProto @UNIX
|
||||
[ makeResponse (makeServer @MyRPC)
|
||||
]
|
||||
|
||||
void $ waitAnyCatchCancel [m1,p1]
|
||||
|
||||
|
||||
|
||||
-- pure ()
|
||||
|
||||
updateIndexPeriodially :: DashBoardPerks m => DashBoardM m ()
|
||||
updateIndexPeriodially = do
|
||||
|
||||
|
||||
api <- asks _refLogAPI
|
||||
|
||||
env <- ask
|
||||
|
||||
changes <- newTQueueIO
|
||||
|
||||
-- queues <- newTVarIO ( mempty :: HashMap RepoLww (TQueue (IO ()) ) )
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
lift $ addJob (withDashBoardEnv env updateIndex)
|
||||
|
||||
p1 <- ContT $ withAsync $ do
|
||||
pause @'Seconds 30
|
||||
forever do
|
||||
rs <- atomically $ peekTQueue changes >> flushTQueue changes
|
||||
addJob (withDashBoardEnv env updateIndex)
|
||||
-- pause @'Seconds 1
|
||||
|
||||
p2 <- pollRepos changes
|
||||
|
||||
p3 <- pollFixmies
|
||||
|
||||
p4 <- pollRepoIndex
|
||||
|
||||
void $ waitAnyCatchCancel [p1,p2,p3,p4]
|
||||
|
||||
where
|
||||
|
||||
pollFixmies = do
|
||||
|
||||
env <- ask
|
||||
|
||||
api <- asks _refChanAPI
|
||||
|
||||
cached <- newTVarIO ( mempty :: HashMap MyRefChan HashRef )
|
||||
|
||||
let chans = selectRepoFixme
|
||||
<&> fmap (,60)
|
||||
|
||||
ContT $ withAsync $ do
|
||||
polling (Polling 10 30) chans $ \(l,r) -> do
|
||||
debug $ yellow "POLL FIXME CHAN" <+> pretty (AsBase58 r)
|
||||
|
||||
void $ runMaybeT do
|
||||
|
||||
new <- lift (callRpcWaitMay @RpcRefChanGet (TimeoutSec 1) api (coerce r))
|
||||
<&> join
|
||||
>>= toMPlus
|
||||
|
||||
old <- readTVarIO cached <&> HM.lookup r
|
||||
|
||||
atomically $ modifyTVar cached (HM.insert r new)
|
||||
|
||||
when (Just new /= old) $ lift do
|
||||
debug $ yellow "fixme refchan changed" <+> "run update" <+> pretty new
|
||||
addJob do
|
||||
-- TODO: this-is-not-100-percent-reliable
|
||||
-- $workflow: backlog
|
||||
-- откуда нам вообще знать, что там всё получилось?
|
||||
void $ try @_ @SomeException (withDashBoardEnv env $ updateFixmeFor l r)
|
||||
|
||||
|
||||
pollRepos changes = do
|
||||
|
||||
cached <- newTVarIO ( mempty :: HashMap MyRefLogKey HashRef )
|
||||
|
||||
api <- asks _refLogAPI
|
||||
let rlogs = selectRefLogs <&> fmap (over _1 (coerce @_ @MyRefLogKey)) . fmap (, 60)
|
||||
|
||||
ContT $ withAsync $ do
|
||||
polling (Polling 10 30) rlogs $ \r -> do
|
||||
|
||||
debug $ yellow "POLL REFLOG" <+> pretty r
|
||||
|
||||
rv <- callRpcWaitMay @RpcRefLogGet (TimeoutSec 1) api (coerce r)
|
||||
<&> join
|
||||
|
||||
old <- readTVarIO cached <&> HM.lookup r
|
||||
|
||||
|
||||
for_ rv $ \x -> do
|
||||
|
||||
atomically $ modifyTVar cached (HM.insert r x)
|
||||
|
||||
when (rv /= old) do
|
||||
debug $ yellow "REFLOG UPDATED" <+> pretty r <+> pretty x
|
||||
atomically $ modifyTVar cached (HM.insert r x)
|
||||
atomically $ writeTQueue changes r
|
||||
|
||||
flip runContT pure $ callCC $ \exit -> do
|
||||
|
||||
lww <- lift (selectLwwByRefLog (RepoRefLog r))
|
||||
>>= maybe (exit ()) pure
|
||||
|
||||
dir <- lift $ repoDataPath (coerce lww)
|
||||
|
||||
here <- doesDirectoryExist dir
|
||||
|
||||
unless here do
|
||||
debug $ red "INIT DATA DIR" <+> pretty dir
|
||||
mkdir dir
|
||||
void $ runProcess $ shell [qc|git --git-dir {dir} init --bare|]
|
||||
|
||||
let cmd = [qc|git --git-dir {dir} hbs2 import {show $ pretty lww}|]
|
||||
debug $ red "SYNC" <+> pretty cmd
|
||||
void $ runProcess $ shell cmd
|
||||
|
||||
pollRepoIndex = do
|
||||
|
||||
api <- asks _refLogAPI
|
||||
let rlogs = selectRefLogs <&> fmap (over _1 (coerce @_ @MyRefLogKey)) . fmap (, 600)
|
||||
|
||||
ContT $ withAsync $ do
|
||||
polling (Polling 1 30) rlogs $ \r -> do
|
||||
lww' <- selectLwwByRefLog (RepoRefLog r)
|
||||
for_ lww' $ addRepoIndexJob . coerce
|
||||
|
||||
quit :: DashBoardPerks m => m ()
|
||||
quit = liftIO exitSuccess
|
||||
|
||||
withMyRPCClient :: ( MonadUnliftIO m )
|
||||
-- , HasTimeLimits UNIX (ServiceProto MyRPC UNIX) m)
|
||||
=> FilePath -> (ServiceCaller MyRPC UNIX -> IO b) -> m b
|
||||
withMyRPCClient soname m = do
|
||||
liftIO do
|
||||
client <- newMessagingUnix False 1.0 soname
|
||||
flip runContT pure do
|
||||
mess <- ContT $ withAsync $ runMessagingUnix client
|
||||
caller <- makeServiceCaller @MyRPC @UNIX (msgUnixSelf client)
|
||||
p2 <- ContT $ withAsync $ runReaderT (runServiceClient caller) client
|
||||
void $ ContT $ bracket none (const $ cancel mess)
|
||||
void $ ContT $ bracket none (const $ cancel p2)
|
||||
liftIO $ m caller
|
||||
|
||||
|
||||
theDict :: forall m . ( DashBoardPerks m
|
||||
-- , HasTimeLimits UNIX (ServiceProto MyRPC UNIX) m
|
||||
) => Dict C (DashBoardM m)
|
||||
theDict = do
|
||||
makeDict @C do
|
||||
-- TODO: write-man-entries
|
||||
myHelpEntry
|
||||
fixmeAllowEntry
|
||||
fixmeAllowDropEntry
|
||||
webEntry
|
||||
portEntry
|
||||
developAssetsEntry
|
||||
baseUrlEntry
|
||||
getRpcSocketEntry
|
||||
rpcPingEntry
|
||||
rpcIndexEntry
|
||||
debugEntries
|
||||
|
||||
where
|
||||
|
||||
myHelpEntry = do
|
||||
entry $ bindMatch "--help" $ nil_ $ \case
|
||||
HelpEntryBound what -> do
|
||||
helpEntry what
|
||||
quit
|
||||
|
||||
[StringLike s] -> helpList False (Just s) >> quit
|
||||
|
||||
_ -> helpList False Nothing >> quit
|
||||
|
||||
fixmeAllowEntry = do
|
||||
brief "allows fixme for given reflog" $
|
||||
args [arg "public-key" "reflog"] $
|
||||
examples [qc|
|
||||
fixme-allow BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP
|
||||
|]
|
||||
$ entry $ bindMatch "fixme-allow" $ nil_ \case
|
||||
[SignPubKeyLike what] -> do
|
||||
lift $ insertFixmeAllowed (RepoRefLog (RefLogKey what))
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
fixmeAllowDropEntry = do
|
||||
brief "drop all allowed fixme records" $
|
||||
examples [qc|
|
||||
fixme-allow:drop
|
||||
|]
|
||||
$ entry $ bindMatch "fixme-allow:drop" $ nil_ \case
|
||||
[] -> do
|
||||
lift $ deleteFixmeAllowed
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
webEntry = do
|
||||
brief "run web interface" $
|
||||
entry $ bindMatch "web" $ nil_ $ const do
|
||||
lift runScotty
|
||||
|
||||
portEntry = do
|
||||
brief "set http port for web interface" $
|
||||
entry $ bindMatch "port" $ nil_ \case
|
||||
[LitIntVal n] -> do
|
||||
tp <- lift $ asks _dashBoardHttpPort
|
||||
atomically $ writeTVar tp (Just (fromIntegral n))
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
|
||||
developAssetsEntry = do
|
||||
entry $ bindMatch "develop-assets" $ nil_ \case
|
||||
[StringLike s] -> do
|
||||
devAssTVar <- lift $ asks _dashBoardDevAssets
|
||||
atomically $ writeTVar devAssTVar (Just s)
|
||||
|
||||
_ -> none
|
||||
|
||||
baseUrlEntry = do
|
||||
entry $ bindMatch "base-url" $ nil_ \case
|
||||
[StringLike s] -> do
|
||||
urlTV <- lift $ asks _dashBoardBaseUrl
|
||||
atomically $ writeTVar urlTV (Just (Text.pack s))
|
||||
_ -> none
|
||||
|
||||
getRpcSocketEntry = do
|
||||
entry $ bindMatch "rpc:socket" $ nil_ $ const do
|
||||
lift getRPCSocket >>= liftIO . maybe exitFailure putStr
|
||||
|
||||
rpcPingEntry = do
|
||||
entry $ bindMatch "ping" $ nil_ $ const $ lift do
|
||||
so <- getRPCSocket >>= orThrowUser "rpc socket down"
|
||||
withMyRPCClient so $ \caller -> do
|
||||
what <- callService @PingRPC caller ()
|
||||
print what
|
||||
|
||||
rpcIndexEntry = do
|
||||
entry $ bindMatch "index:now" $ nil_ $ const $ lift do
|
||||
so <- getRPCSocket >>= orThrowUser "rpc socket down"
|
||||
withMyRPCClient so $ \caller -> do
|
||||
void $ callService @IndexNowRPC caller ()
|
||||
|
||||
-- TODO: ASAP-hide-debug-functions-from-help
|
||||
|
||||
debugEntries = do
|
||||
|
||||
entry $ bindMatch "debug:cache:ignore:on" $ nil_ $ const $ lift do
|
||||
t <- asks _dashBoardIndexIgnoreCaches
|
||||
atomically $ writeTVar t True
|
||||
|
||||
entry $ bindMatch "debug:cache:ignore:off" $ nil_ $ const $ lift do
|
||||
t <- asks _dashBoardIndexIgnoreCaches
|
||||
atomically $ writeTVar t False
|
||||
|
||||
entry $ bindMatch "debug:build-commit-index" $ nil_ $ \case
|
||||
[SignPubKeyLike lw] -> lift do
|
||||
buildCommitTreeIndex (LWWRefKey lw)
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
|
||||
entry $ bindMatch "debug:build-single-commit-index" $ nil_ $ \case
|
||||
[SignPubKeyLike lw, StringLike h'] -> lift do
|
||||
|
||||
h <- fromStringMay @GitHash h'
|
||||
& orThrowUser ("invalid git object hash" <+> pretty h')
|
||||
|
||||
buildSingleCommitTreeIndex (LWWRefKey lw) h
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
-- rs <- selectRepoFixme
|
||||
-- for_ rs $ \(r,f) -> do
|
||||
-- liftIO $ print $ pretty r <+> pretty (AsBase58 f)
|
||||
|
||||
|
||||
entry $ bindMatch "debug:select-repo-fixme" $ nil_ $ const $ lift do
|
||||
rs <- selectRepoFixme
|
||||
for_ rs $ \(r,f) -> do
|
||||
liftIO $ print $ pretty r <+> pretty (AsBase58 f)
|
||||
|
||||
entry $ bindMatch "debug:check-fixme-allowed" $ nil_ $ \case
|
||||
[SignPubKeyLike s] -> do
|
||||
what <- lift $ checkFixmeAllowed (RepoLww (LWWRefKey s))
|
||||
liftIO $ print $ pretty what
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
|
||||
entry $ bindMatch "debug:test-with-fixme" $ nil_ $ \case
|
||||
[SignPubKeyLike s] -> lift do
|
||||
r <- listFixme (RepoLww (LWWRefKey s)) ()
|
||||
for_ r $ \f -> do
|
||||
liftIO $ print $ pretty f
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
entry $ bindMatch "debug:count-fixme" $ nil_ $ \case
|
||||
[SignPubKeyLike s] -> lift do
|
||||
r <- countFixme (RepoLww (LWWRefKey s))
|
||||
liftIO $ print $ pretty r
|
||||
|
||||
_ -> throwIO $ BadFormException @C nil
|
||||
|
||||
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
argz <- getArgs
|
||||
cli <- parseTop (unlines $ unwords <$> splitForms argz)
|
||||
& either (error.show) pure
|
||||
|
||||
let dict = theDict
|
||||
|
||||
void $ runDashBoardM $ do
|
||||
run dict cli
|
|
@ -1,201 +0,0 @@
|
|||
{-# Language TemplateHaskell #-}
|
||||
module HBS2.Git.Web.Assets where
|
||||
|
||||
import Data.FileEmbed
|
||||
import Data.ByteString
|
||||
import Data.Text (Text)
|
||||
import Text.InterpolatedString.Perl6 (qc)
|
||||
import Lucid.Base
|
||||
|
||||
version :: Int
|
||||
version = 8
|
||||
|
||||
assetsDir :: [(FilePath, ByteString)]
|
||||
assetsDir = $(embedDir "hbs2-git-dashboard-assets/assets")
|
||||
|
||||
data IconType
|
||||
= IconCopy
|
||||
| IconCopyDone
|
||||
| IconLockClosed
|
||||
| IconGitCommit
|
||||
| IconGitFork
|
||||
| IconGitBranch
|
||||
| IconTag
|
||||
| IconFolderFilled
|
||||
| IconHaskell
|
||||
| IconMarkdown
|
||||
| IconNix
|
||||
| IconBash
|
||||
| IconPython
|
||||
| IconJavaScript
|
||||
| IconSql
|
||||
| IconSettingsFilled
|
||||
| IconFileFilled
|
||||
| IconRefresh
|
||||
| IconArrowUturnLeft
|
||||
| IconLicense
|
||||
| IconPinned
|
||||
| IconFixme
|
||||
|
||||
svgIcon :: Monad m => IconType -> HtmlT m ()
|
||||
svgIcon = toHtmlRaw . svgIconText
|
||||
|
||||
svgIconText :: IconType -> Text
|
||||
|
||||
svgIconText IconCopy = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z" />
|
||||
<path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconCopyDone = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z" />
|
||||
<path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" />
|
||||
<path d="M11 14l2 2l4 -4" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconLockClosed = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lock" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6z" />
|
||||
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" />
|
||||
<path d="M8 11v-4a4 4 0 1 1 8 0v4" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconGitCommit = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-git-commit" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||
<path d="M12 3l0 6" />
|
||||
<path d="M12 15l0 6" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconGitFork = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-git-fork" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M7 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M17 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2" />
|
||||
<path d="M12 12l0 4" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconGitBranch = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-git-branch" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M7 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M17 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M7 8l0 8" />
|
||||
<path d="M9 18h6a2 2 0 0 0 2 -2v-5" />
|
||||
<path d="M14 14l3 -3l3 3" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconTag = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3z" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconFolderFilled = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconHaskell = [qc|<svg role="img" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Haskell</title>
|
||||
<path d="M0 3.535L5.647 12 0 20.465h4.235L9.883 12 4.235 3.535zm5.647 0L11.294 12l-5.647 8.465h4.235l3.53-5.29 3.53 5.29h4.234L9.883 3.535zm8.941 4.938l1.883 2.822H24V8.473zm2.824 4.232l1.882 2.822H24v-2.822z"/>
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconMarkdown = [qc|<svg role="img" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Markdown</title>
|
||||
<path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/>
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconNix = [qc|<svg role="img" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Nix</title>
|
||||
<path d="M7.352 1.592l-1.364.002L5.32 2.75l1.557 2.713-3.137-.008-1.32 2.34H14.11l-1.353-2.332-3.192-.006-2.214-3.865zm6.175 0l-2.687.025 5.846 10.127 1.341-2.34-1.59-2.765 2.24-3.85-.683-1.182h-1.336l-1.57 2.705-1.56-2.72zm6.887 4.195l-5.846 10.125 2.696-.008 1.601-2.76 4.453.016.682-1.183-.666-1.157-3.13-.008L21.778 8.1l-1.365-2.313zM9.432 8.086l-2.696.008-1.601 2.76-4.453-.016L0 12.02l.666 1.157 3.13.008-1.575 2.71 1.365 2.315L9.432 8.086zM7.33 12.25l-.006.01-.002-.004-1.342 2.34 1.59 2.765-2.24 3.85.684 1.182H7.35l.004-.006h.001l1.567-2.698 1.558 2.72 2.688-.026-.004-.006h.01L7.33 12.25zm2.55 3.93l1.354 2.332 3.192.006 2.215 3.865 1.363-.002.668-1.156-1.557-2.713 3.137.008 1.32-2.34H9.881Z"/>
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconBash = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-terminal-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M8 9l3 3l-3 3" />
|
||||
<path d="M13 15l3 0" />
|
||||
<path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconPython = [qc|<svg role="img" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Python</title>
|
||||
<path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/>
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconJavaScript = [qc|<svg role="img" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>JavaScript</title>
|
||||
<path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/>
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconSql = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-sql" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
|
||||
<path d="M5 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75" />
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4" />
|
||||
<path d="M18 15v6h2" />
|
||||
<path d="M13 15a2 2 0 0 1 2 2v2a2 2 0 1 1 -4 0v-2a2 2 0 0 1 2 -2z" />
|
||||
<path d="M14 20l1.5 1.5" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconSettingsFilled = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-settings-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14.647 4.081a.724 .724 0 0 0 1.08 .448c2.439 -1.485 5.23 1.305 3.745 3.744a.724 .724 0 0 0 .447 1.08c2.775 .673 2.775 4.62 0 5.294a.724 .724 0 0 0 -.448 1.08c1.485 2.439 -1.305 5.23 -3.744 3.745a.724 .724 0 0 0 -1.08 .447c-.673 2.775 -4.62 2.775 -5.294 0a.724 .724 0 0 0 -1.08 -.448c-2.439 1.485 -5.23 -1.305 -3.745 -3.744a.724 .724 0 0 0 -.447 -1.08c-2.775 -.673 -2.775 -4.62 0 -5.294a.724 .724 0 0 0 .448 -1.08c-1.485 -2.439 1.305 -5.23 3.744 -3.745a.722 .722 0 0 0 1.08 -.447c.673 -2.775 4.62 -2.775 5.294 0zm-2.647 4.919a3 3 0 1 0 0 6a3 3 0 0 0 0 -6z" stroke-width="0" fill="currentColor" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconFileFilled = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005h5z" stroke-width="0" fill="currentColor" />
|
||||
<path d="M19 7h-4l-.001 -4.001z" stroke-width="0" fill="currentColor" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconRefresh = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
|
||||
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconArrowUturnLeft = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-uturn-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 14l-4 -4l4 -4" />
|
||||
<path d="M5 10h11a4 4 0 1 1 0 8h-1" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconLicense = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-license" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 21h-9a3 3 0 0 1 -3 -3v-1h10v2a2 2 0 0 0 4 0v-14a2 2 0 1 1 2 2h-2m2 -4h-11a3 3 0 0 0 -3 3v11" />
|
||||
<path d="M9 7l4 0" />
|
||||
<path d="M9 11l4 0" />
|
||||
</svg>|]
|
||||
|
||||
svgIconText IconPinned = [qc|<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pinned" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 4v6l-2 4v2h10v-2l-2 -4v-6" />
|
||||
<path d="M12 16l0 5" />
|
||||
<path d="M8 4l8 0" />
|
||||
</svg>|]
|
||||
|
||||
|
||||
svgIconText IconFixme = [qc|
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-stack-3">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 2l-8 4l8 4l8 -4l-8 -4" />
|
||||
<path d="M4 10l8 4l8 -4" />
|
||||
<path d="M4 18l8 4l8 -4" />
|
||||
<path d="M4 14l8 4l8 -4" />
|
||||
</svg>|]
|
||||
|
||||
|
|
@ -1,370 +0,0 @@
|
|||
/* fastpok CSS start */
|
||||
|
||||
:root {
|
||||
--pico-form-element-spacing-vertical: .5rem;
|
||||
--pico-form-element-spacing-horizontal: .625rem;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
--pico-border-radius: inherit;
|
||||
}
|
||||
|
||||
[role=search] {
|
||||
--pico-border-radius: inherit;
|
||||
}
|
||||
|
||||
[role=search]>:first-child {
|
||||
border-top-left-radius: var(--pico-border-radius);
|
||||
border-bottom-left-radius: var(--pico-border-radius);
|
||||
}
|
||||
|
||||
[role=search]>:last-child {
|
||||
border-top-right-radius: var(--pico-border-radius);
|
||||
border-bottom-right-radius: var(--pico-border-radius);
|
||||
}
|
||||
|
||||
body>footer, body>header, body>main {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
header>nav {
|
||||
border-bottom: var(--pico-border-width) solid var(--pico-muted-border-color);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 20rem;
|
||||
flex-shrink: 0;
|
||||
padding-top: var(--pico-block-spacing-vertical);
|
||||
padding-right: var(--pico-block-spacing-horizontal);
|
||||
padding-bottom: var(--pico-block-spacing-vertical);
|
||||
border-right: var(--pico-border-width) solid var(--pico-muted-border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: var(--pico-block-spacing-vertical);
|
||||
padding-bottom: var(--pico-block-spacing-vertical);
|
||||
padding-left: var(--pico-block-spacing-horizontal);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
article {
|
||||
border: var(--pico-border-width) solid var(--pico-card-border-color);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.repo-list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--pico-block-spacing-horizontal);
|
||||
}
|
||||
|
||||
.repo-list-item-link-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: var(--pico-typography-spacing-vertical);
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
margin-left: calc(var(--pico-spacing) * .5);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
color: var(--pico-secondary);
|
||||
transition: color var(--pico-transition);
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
color: var(--pico-secondary-hover);
|
||||
}
|
||||
|
||||
.copy-button .icon {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.inline-icon-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inline-icon-wrapper .icon {
|
||||
margin-right: calc(var(--pico-spacing) * .25);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-bottom: var(--pico-block-spacing-vertical);
|
||||
}
|
||||
|
||||
.repo-menu {
|
||||
--pico-nav-breadcrumb-divider: '|';
|
||||
}
|
||||
|
||||
.repo-menu li.active {
|
||||
color: var(--pico-primary);
|
||||
}
|
||||
|
||||
aside li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
aside ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
aside li :where(a,[role=link]):not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
margin-bottom: calc(var(--pico-typography-spacing-vertical) * .25);
|
||||
}
|
||||
|
||||
.issue-info-card {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.issue-info-card>header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.issue-info-card>header h5 {
|
||||
color: inherit;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.issue-info-table tr:hover {
|
||||
background-color: var(--pico-background-color);
|
||||
}
|
||||
|
||||
.issue-info-table td,
|
||||
.issue-info-table th {
|
||||
border-bottom-color: var(--pico-card-border-color);
|
||||
}
|
||||
|
||||
.issue-info-table tr:last-child>td,
|
||||
.issue-info-table tr:last-child>th {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.issue-info-table tr:last-child>td:first-child,
|
||||
.issue-info-table tr:last-child>th:first-child {
|
||||
border-bottom-left-radius: var(--pico-border-radius);
|
||||
}
|
||||
|
||||
.issue-info-table tr:last-child>td:last-child,
|
||||
.issue-info-table tr:last-child>th:last-child {
|
||||
border-bottom-right-radius: var(--pico-border-radius);
|
||||
}
|
||||
|
||||
.issue-info-card .issue-id {
|
||||
cursor: pointer;
|
||||
border-bottom: none;
|
||||
color: var(--pico-secondary);
|
||||
}
|
||||
|
||||
|
||||
.issue-info-card .issue-id:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--pico-secondary-hover);
|
||||
}
|
||||
|
||||
/* Tailwind-style classes */
|
||||
.mb-0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: var(--pico-spacing);
|
||||
}
|
||||
|
||||
.p-0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.py-0 {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--pico-secondary);
|
||||
}
|
||||
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* fastpok CSS end */
|
||||
|
||||
|
||||
ul.misc-menu {
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
ul.misc-menu li {
|
||||
padding: 0 0 0 0;
|
||||
margin-right: 1em;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.tree {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
td.tree-locator {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
td.tree-locator span {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
tr.commit-brief-title td,
|
||||
tr.commit-brief-title th {
|
||||
border-bottom: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr.commit-brief-details td,
|
||||
tr.commit-brief-details th {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
td.commit-brief-title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr.commit-brief-last td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
tr.commit-brief-last th {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td.commit-icon {
|
||||
width: 4rem;
|
||||
/* width: px; */
|
||||
}
|
||||
|
||||
td.commit-hash {
|
||||
width: 10rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.minimal {
|
||||
}
|
||||
|
||||
table.minimal tr td {
|
||||
border: none;
|
||||
padding: 0.15em;
|
||||
}
|
||||
|
||||
table.minimal tr {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.lim-text {
|
||||
max-width: 80ch;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: auto; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; overflow: auto; }
|
||||
div.sourceCode { margin: 1em 0; overflow: auto; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; max-width: 120rem; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ef2929; } /* Alert */
|
||||
code span.an { color: #8f5902; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #204a87; } /* Attribute */
|
||||
code span.bn { color: #0000cf; } /* BaseN */
|
||||
code span.cf { color: #204a87; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4e9a06; } /* Char */
|
||||
code span.cn { color: #8f5902; } /* Constant */
|
||||
code span.co { color: #8f5902; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #8f5902; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #8f5902; font-weight: bold; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #204a87; } /* DataType */
|
||||
code span.dv { color: #0000cf; } /* DecVal */
|
||||
code span.er { color: #a40000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #0000cf; } /* Float */
|
||||
code span.fu { color: #204a87; font-weight: bold; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #8f5902; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #204a87; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #ce5c00; font-weight: bold; } /* Operator */
|
||||
code span.ot { color: #8f5902; } /* Other */
|
||||
code span.pp { color: #8f5902; font-style: italic; } /* Preprocessor */
|
||||
code span.sc { color: #ce5c00; font-weight: bold; } /* SpecialChar */
|
||||
code span.ss { color: #4e9a06; } /* SpecialString */
|
||||
code span.st { color: #4e9a06; } /* String */
|
||||
code span.va { color: #000000; } /* Variable */
|
||||
code span.vs { color: #4e9a06; } /* VerbatimString */
|
||||
code span.wa { color: #8f5902; font-weight: bold; font-style: italic; } /* Warning */
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -1,142 +0,0 @@
|
|||
module HBS2.Git.DashBoard.Fixme
|
||||
( F.HasPredicate(..)
|
||||
, F.HasLimit(..)
|
||||
, HasItemOrder(..)
|
||||
, ItemOrder(..)
|
||||
, Reversed(..)
|
||||
, F.SelectPredicate(..)
|
||||
, WithLimit(..)
|
||||
, QueryOffset
|
||||
, QueryLimit
|
||||
, runInFixme
|
||||
, countFixme
|
||||
, countFixmeByAttribute
|
||||
, listFixme
|
||||
, getFixme
|
||||
, RunInFixmeError(..)
|
||||
, Fixme(..)
|
||||
, FixmeKey(..)
|
||||
, FixmeTitle(..)
|
||||
, FixmeTag(..)
|
||||
, FixmePlainLine(..)
|
||||
, FixmeAttrName(..)
|
||||
, FixmeAttrVal(..)
|
||||
, FixmeOpts(..)
|
||||
, fixmePageSize
|
||||
, fixmeGet
|
||||
) where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
|
||||
import HBS2.OrDie
|
||||
|
||||
import Fixme.State qualified as F
|
||||
import Fixme.State ( HasPredicate(..)
|
||||
, HasLimit(..)
|
||||
, HasItemOrder(..)
|
||||
, WithLimit(..)
|
||||
, QueryOffset
|
||||
, QueryLimit
|
||||
, ItemOrder
|
||||
, Reversed
|
||||
)
|
||||
import Fixme.Types
|
||||
import Fixme.Config
|
||||
|
||||
import DBPipe.SQLite (shutdown)
|
||||
|
||||
import Data.Either
|
||||
import Data.Generics.Product.Fields (field)
|
||||
|
||||
data RunInFixmeError =
|
||||
FixmeRefChanNotFound RepoLww
|
||||
deriving stock (Generic, Typeable, Show)
|
||||
|
||||
instance Exception RunInFixmeError
|
||||
|
||||
fixmePageSize :: QueryLimit
|
||||
fixmePageSize = 100
|
||||
|
||||
|
||||
-- TODO: less-hacky-approach
|
||||
-- этот код подразумевает, что мы знаем довольно много деталей
|
||||
-- реализации про fixme-new
|
||||
--
|
||||
-- Хорошо бы как-то абстрагировать, изолировать и т.п.
|
||||
--
|
||||
runInFixme :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> RepoLww
|
||||
-> FixmeM m a
|
||||
-> m a
|
||||
|
||||
runInFixme repo m = do
|
||||
|
||||
denv <- ask
|
||||
|
||||
fixmeRChan <- withDashBoardEnv denv $ selectRepoFixmeRefChan repo
|
||||
>>= orThrow (FixmeRefChanNotFound repo)
|
||||
|
||||
p <- fixmeDataPath fixmeRChan
|
||||
|
||||
-- TODO: check-if-database-exists
|
||||
|
||||
fenv <- fixmeEnvBare
|
||||
fo <- newTVarIO (FixmeOpts True)
|
||||
|
||||
twd <- newTVarIO p
|
||||
let fenvNew = fenv & set (field @"fixmeEnvWorkDir") twd
|
||||
& set (field @"fixmeEnvOpts") fo
|
||||
|
||||
flip runContT pure do
|
||||
dbe <- lift $ withFixmeEnv fenvNew $ F.withState ask
|
||||
|
||||
void $ ContT $ bracket none (const $ shutdown False dbe)
|
||||
|
||||
lift $ withFixmeEnv fenvNew do
|
||||
dbp <- localDBPath
|
||||
wd <- fixmeWorkDir
|
||||
cfg <- localConfig
|
||||
trace $ "fixme:dir" <+> pretty wd
|
||||
trace $ "fixme:config" <+> pretty cfg
|
||||
trace $ "fixme:db" <+> pretty dbp
|
||||
|
||||
m
|
||||
|
||||
listFixme :: ( DashBoardPerks m
|
||||
, MonadReader DashBoardEnv m
|
||||
, HasPredicate q
|
||||
, HasLimit q
|
||||
, HasItemOrder q
|
||||
) => RepoLww -> q -> m [Fixme]
|
||||
listFixme repo q = do
|
||||
runInFixme repo $ F.listFixme q
|
||||
-- FIXME: error-handling
|
||||
-- at least print log entry
|
||||
& try @_ @SomeException
|
||||
<&> fromRight mempty
|
||||
|
||||
|
||||
getFixme :: ( DashBoardPerks m
|
||||
, MonadReader DashBoardEnv m
|
||||
) => RepoLww -> FixmeKey -> m (Maybe Fixme)
|
||||
getFixme repo fk = do
|
||||
-- FIXME: error-handling
|
||||
-- at least print log entry
|
||||
try @_ @SomeException (runInFixme repo $ runMaybeT do
|
||||
k <- lift (F.selectFixmeKey (coerce fk)) >>= toMPlus
|
||||
lift (F.getFixme k) >>= toMPlus ) <&> fromRight Nothing
|
||||
|
||||
countFixme :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoLww -> m (Maybe Int)
|
||||
countFixme repo = do
|
||||
runInFixme repo $ F.countFixme
|
||||
& try @_ @SomeException
|
||||
<&> either (const Nothing) Just
|
||||
|
||||
countFixmeByAttribute :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoLww -> String -> m [(FixmeAttrVal, Int)]
|
||||
countFixmeByAttribute repo name = do
|
||||
runInFixme repo $ F.countByAttribute (fromString name)
|
||||
& try @_ @SomeException
|
||||
<&> fromRight mempty
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
{-# Language PatternSynonyms #-}
|
||||
{-# Language ViewPatterns #-}
|
||||
module HBS2.Git.DashBoard.Manifest where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.Data.RepoHead
|
||||
|
||||
import Data.Text qualified as Text
|
||||
import Data.Either
|
||||
import Streaming.Prelude qualified as S
|
||||
|
||||
|
||||
pattern FixmeRefChanP :: forall {c} . PubKey Sign HBS2Basic -> Syntax c
|
||||
pattern FixmeRefChanP x <- ListVal [ SymbolVal "fixme:"
|
||||
, ListVal [ SymbolVal "refchan", SignPubKeyLike x
|
||||
]]
|
||||
|
||||
|
||||
pattern PinnedRefBlob :: forall {c}. Text -> Text -> GitHash -> Syntax c
|
||||
pattern PinnedRefBlob syn name hash <- ListVal [ SymbolVal "blob"
|
||||
, SymbolVal (Id syn)
|
||||
, LitStrVal name
|
||||
, asGitHash -> Just hash
|
||||
]
|
||||
{-# COMPLETE PinnedRefBlob #-}
|
||||
|
||||
asGitHash :: forall c . Syntax c -> Maybe GitHash
|
||||
asGitHash = \case
|
||||
LitStrVal s -> fromStringMay (Text.unpack s)
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
|
||||
parseManifest :: Monad m => RepoHead -> m ([Syntax C], Text)
|
||||
parseManifest mhead = do
|
||||
|
||||
let rawManifest = maybe mempty Text.lines (_repoManifest mhead)
|
||||
|
||||
w <- S.toList_ do
|
||||
flip fix rawManifest $ \next ss -> do
|
||||
case ss of
|
||||
( "" : rest ) -> S.yield (Right (Text.stripStart (Text.unlines rest)))
|
||||
( a : rest ) -> S.yield (Left a ) >> next rest
|
||||
[] -> pure ()
|
||||
|
||||
let meta = Text.unlines (lefts w)
|
||||
& Text.unpack
|
||||
& parseTop
|
||||
& fromRight mempty
|
||||
|
||||
let manifest = mconcat $ rights w
|
||||
|
||||
pure (meta, manifest)
|
||||
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
{-# LANGUAGE PatternSynonyms #-}
|
||||
module HBS2.Git.DashBoard.Prelude
|
||||
( module HBS2.Git.DashBoard.Prelude
|
||||
, module HBS2.Prelude.Plated
|
||||
, module HBS2.Data.Types.Refs
|
||||
, module HBS2.Base58
|
||||
, module HBS2.Merkle
|
||||
, module HBS2.Net.Proto.Service
|
||||
, module HBS2.Storage
|
||||
, module API
|
||||
, module Config
|
||||
, module Logger
|
||||
, module Maybe
|
||||
, module Reader
|
||||
, module Coerce
|
||||
, module TransCont
|
||||
, module TransMaybe
|
||||
, module Lens.Micro.Platform
|
||||
, module UnliftIO
|
||||
, module Codec.Serialise
|
||||
, GitRef(..), GitHash(..), GitObjectType(..)
|
||||
, pattern SignPubKeyLike
|
||||
, qc, q
|
||||
) where
|
||||
|
||||
import HBS2.Data.Types.Refs
|
||||
import HBS2.Base58
|
||||
import HBS2.Net.Proto.Service hiding (encode,decode)
|
||||
import HBS2.Prelude.Plated
|
||||
import HBS2.Storage
|
||||
import HBS2.Merkle
|
||||
|
||||
import HBS2.System.Logger.Simple.ANSI as Logger
|
||||
import HBS2.Misc.PrettyStuff as Logger
|
||||
|
||||
import HBS2.Net.Auth.Credentials
|
||||
|
||||
import HBS2.Peer.RPC.API.RefChan as API
|
||||
import HBS2.Peer.RPC.API.RefLog as API
|
||||
import HBS2.Peer.RPC.API.Peer as API
|
||||
import HBS2.Peer.RPC.API.LWWRef as API
|
||||
|
||||
import HBS2.Peer.Proto.RefLog as API
|
||||
import HBS2.Peer.Proto.LWWRef as API
|
||||
import HBS2.Peer.Proto.RefChan.Types as API
|
||||
import HBS2.Peer.Proto.RefChan.RefChanUpdate as API
|
||||
|
||||
import HBS2.Git.Local
|
||||
|
||||
import Data.Config.Suckless as Config
|
||||
|
||||
import Text.InterpolatedString.Perl6 (qc,q)
|
||||
|
||||
import Data.Maybe as Maybe
|
||||
import Control.Monad.Reader as Reader
|
||||
import Data.Coerce as Coerce
|
||||
import Control.Monad.Trans.Cont as TransCont
|
||||
import Control.Monad.Trans.Maybe as TransMaybe
|
||||
|
||||
import Lens.Micro.Platform hiding (at)
|
||||
|
||||
import UnliftIO
|
||||
|
||||
import Codec.Serialise
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +0,0 @@
|
|||
{-# LANGUAGE TemplateHaskell #-}
|
||||
module HBS2.Git.DashBoard.State.Commits where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
|
||||
import HBS2.Git.Local
|
||||
import HBS2.Git.Local.CLI
|
||||
|
||||
import Data.ByteString.Lazy.Char8 qualified as LBS8
|
||||
import Data.Text.Encoding qualified as Text
|
||||
import Data.Text qualified as Text
|
||||
import Data.Time (UTCTime,LocalTime)
|
||||
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
|
||||
import Data.Either
|
||||
|
||||
import Streaming.Prelude qualified as S
|
||||
|
||||
{- HLINT ignore "Functor law" -}
|
||||
|
||||
class Monoid a => FromQueryParams a where
|
||||
fromQueryParams :: [(Text,Text)] -> a
|
||||
|
||||
data CommitListStyle = CommitListBrief
|
||||
|
||||
data SelectCommitsPred =
|
||||
SelectCommitsPred
|
||||
{ _commitListStyle :: CommitListStyle
|
||||
, _commitPredOffset :: Int
|
||||
, _commitPredLimit :: Int
|
||||
, _commitRef :: Maybe GitRef
|
||||
}
|
||||
|
||||
makeLenses ''SelectCommitsPred
|
||||
|
||||
instance Semigroup SelectCommitsPred where
|
||||
(<>) _ b = mempty & set commitListStyle (view commitListStyle b)
|
||||
& set commitPredOffset (view commitPredOffset b)
|
||||
& set commitPredLimit (view commitPredLimit b)
|
||||
& set commitRef (view commitRef b)
|
||||
|
||||
instance Monoid SelectCommitsPred where
|
||||
mempty = SelectCommitsPred CommitListBrief 0 100 Nothing
|
||||
|
||||
briefCommits :: SelectCommitsPred
|
||||
briefCommits = mempty
|
||||
|
||||
|
||||
instance FromQueryParams SelectCommitsPred where
|
||||
fromQueryParams args = do
|
||||
let val = headMay [ GitRef (fromString (Text.unpack v)) | ("ref", v) <- args ]
|
||||
mempty & set commitRef val
|
||||
|
||||
newtype Author = Author Text
|
||||
deriving stock (Generic,Data)
|
||||
deriving newtype (Show)
|
||||
|
||||
|
||||
newtype CommitListItemHash = CommitListItemHash GitHash
|
||||
deriving stock (Generic,Data)
|
||||
deriving newtype (Show,Pretty)
|
||||
|
||||
newtype CommitListItemTime = CommitListItemTime Integer
|
||||
deriving stock (Generic,Data)
|
||||
deriving newtype (Show)
|
||||
|
||||
newtype CommitListItemTitle = CommitListItemTitle Text
|
||||
deriving stock (Generic,Data)
|
||||
deriving newtype (Show)
|
||||
|
||||
newtype CommitListItemAuthor = CommitListItemAuthor Author
|
||||
deriving stock (Generic,Data)
|
||||
deriving newtype (Show)
|
||||
|
||||
data CommitListItem =
|
||||
CommitListItemBrief
|
||||
{ commitListHash :: CommitListItemHash
|
||||
, commitListTime :: CommitListItemTime
|
||||
, commitListTitle :: CommitListItemTitle
|
||||
, commitListAuthor :: CommitListItemAuthor
|
||||
}
|
||||
deriving stock (Generic,Data)
|
||||
|
||||
selectCommits :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> SelectCommitsPred
|
||||
-> m [CommitListItem]
|
||||
|
||||
selectCommits lww SelectCommitsPred{..} = do
|
||||
let lim = _commitPredLimit
|
||||
let off = _commitPredOffset
|
||||
let delim = "|||" :: Text
|
||||
dir <- repoDataPath lww
|
||||
|
||||
let what = maybe "--all" (show . pretty) _commitRef
|
||||
|
||||
let cmd = case _commitListStyle of
|
||||
CommitListBrief -> do
|
||||
let fmt = [qc|--pretty=format:"%H{delim}%at{delim}%an{delim}%s"|] :: String
|
||||
[qc|git --git-dir={dir} log {what} --max-count {lim} --skip {off} {fmt}|]
|
||||
|
||||
debug $ red "selectCommits" <+> pretty cmd
|
||||
|
||||
ls <- gitRunCommand cmd
|
||||
<&> fromRight mempty
|
||||
<&> LBS8.lines
|
||||
<&> fmap (Text.decodeUtf8 . LBS8.toStrict)
|
||||
|
||||
S.toList_ do
|
||||
for_ ls $ \l -> do
|
||||
case Text.splitOn "|||" l of
|
||||
z@[cohash,ts,au,msg] -> do
|
||||
|
||||
let utc = readMay @Integer (Text.unpack ts)
|
||||
<&> CommitListItemTime
|
||||
|
||||
let hash = fromStringMay @GitHash (Text.unpack cohash)
|
||||
<&> CommitListItemHash
|
||||
|
||||
let co = CommitListItemBrief
|
||||
<$> hash
|
||||
<*> utc
|
||||
<*> pure (CommitListItemTitle msg)
|
||||
<*> pure (CommitListItemAuthor (Author au))
|
||||
|
||||
maybe1 co none S.yield
|
||||
|
||||
_ -> none
|
||||
|
||||
getCommitRawBrief :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> GitHash
|
||||
-> m Text
|
||||
|
||||
getCommitRawBrief lww hash = do
|
||||
|
||||
dir <- repoDataPath lww
|
||||
|
||||
let cmd = [qc|git --git-dir={dir} show --stat {pretty hash}|]
|
||||
|
||||
debug $ red "getCommitRawBrief" <+> viaShow cmd
|
||||
|
||||
gitRunCommand cmd
|
||||
<&> fromRight mempty
|
||||
<&> Text.decodeUtf8 . LBS8.toStrict
|
||||
|
||||
getCommitRawPatch :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> GitHash
|
||||
-> m Text
|
||||
|
||||
getCommitRawPatch lww hash = do
|
||||
|
||||
dir <- repoDataPath lww
|
||||
|
||||
let cmd = [qc|git --git-dir={dir} show {pretty hash}|]
|
||||
|
||||
debug $ red "getCommitRawPatch" <+> viaShow cmd
|
||||
|
||||
gitRunCommand cmd
|
||||
<&> fromRight mempty
|
||||
<&> Text.decodeUtf8 . LBS8.toStrict
|
|
@ -1,20 +0,0 @@
|
|||
module HBS2.Git.DashBoard.State.Index
|
||||
( module HBS2.Git.DashBoard.State.Index
|
||||
, module HBS2.Git.DashBoard.State.Index.Channels
|
||||
, module HBS2.Git.DashBoard.State.Index.Peer
|
||||
|
||||
) where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State.Index.Channels
|
||||
import HBS2.Git.DashBoard.State.Index.Peer
|
||||
|
||||
updateIndex :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m ()
|
||||
updateIndex = do
|
||||
debug "updateIndex"
|
||||
updateIndexFromPeer
|
||||
updateIndexFromChannels
|
||||
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
module HBS2.Git.DashBoard.State.Index.Channels where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
|
||||
import DBPipe.SQLite hiding (insert)
|
||||
import DBPipe.SQLite.Generic as G
|
||||
|
||||
import Streaming.Prelude qualified as S
|
||||
|
||||
updateIndexFromChannels :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m ()
|
||||
updateIndexFromChannels = do
|
||||
debug "updateIndexChannels"
|
||||
|
||||
rchanAPI <- asks _refChanAPI
|
||||
sto <- asks _sto
|
||||
|
||||
flip runContT pure do
|
||||
|
||||
es <- lift getIndexEntries
|
||||
|
||||
for_ es $ \rc -> do
|
||||
callCC \next -> do
|
||||
debug $ red (pretty (AsBase58 rc))
|
||||
|
||||
h <- lift (callRpcWaitMay @RpcRefChanGet (1 :: Timeout 'Seconds) rchanAPI rc)
|
||||
<&> join
|
||||
>>= maybe (next ()) pure
|
||||
|
||||
debug $ "rechan val" <+> red (pretty h)
|
||||
|
||||
txs <- S.toList_ do
|
||||
walkMerkle @[HashRef] (coerce h) (getBlock sto) $ \case
|
||||
Left{} -> pure ()
|
||||
Right hs -> mapM_ S.yield hs
|
||||
|
||||
for_ txs $ \txh -> void $ runMaybeT do
|
||||
|
||||
done <- lift $ lift $ withState do
|
||||
select @(Only Int)
|
||||
[qc|select 1 from processed where hash = ? limit 1|]
|
||||
(Only (TxHash txh)) <&> isJust . listToMaybe
|
||||
|
||||
guard (not done)
|
||||
|
||||
tx@GitIndexTx{..} <- getBlock sto (coerce txh)
|
||||
>>= toMPlus
|
||||
>>= readProposeTranMay @(GitIndexTx 'HBS2Basic) @L4Proto
|
||||
>>= toMPlus
|
||||
|
||||
lift $ lift $ withState $ transactional do
|
||||
let nm = [ RepoName n | GitIndexRepoName n <- universeBi gitIndexTxPayload ] & headMay
|
||||
let bri = [ RepoBrief n | GitIndexRepoBrief n <- universeBi gitIndexTxPayload ] & headMay
|
||||
|
||||
insert @RepoTable $ onConflictIgnore @RepoTable (Only (RepoLww gitIndexTxRef))
|
||||
|
||||
insert @RepoChannelTable $
|
||||
onConflictIgnore @RepoChannelTable (RepoLww gitIndexTxRef, RepoChannel rc)
|
||||
|
||||
-- FIXME: on-conflict-update!
|
||||
for_ nm $ \n -> do
|
||||
insert @RepoNameTable $
|
||||
onConflictIgnore @RepoNameTable (RepoLww gitIndexTxRef, n)
|
||||
|
||||
for_ bri $ \n -> do
|
||||
insert @RepoBriefTable $
|
||||
onConflictIgnore @RepoBriefTable (RepoLww gitIndexTxRef, n)
|
||||
|
||||
lift $ withState $ transactional do
|
||||
for_ txs $ \t -> do
|
||||
insert @TxProcessedTable $ onConflictIgnore @TxProcessedTable (Only (TxHash t))
|
||||
|
||||
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
module HBS2.Git.DashBoard.State.Index.Peer where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Manifest
|
||||
import HBS2.Git.Data.LWWBlock
|
||||
import HBS2.Git.Data.Tx.Git
|
||||
|
||||
import HBS2.Hash
|
||||
|
||||
import HBS2.System.Dir
|
||||
|
||||
import Streaming.Prelude qualified as S
|
||||
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import System.Process.Typed
|
||||
|
||||
{- HLINT ignore "Functor law" -}
|
||||
|
||||
seconds = TimeoutSec
|
||||
|
||||
|
||||
addRepoIndexJob :: (DashBoardPerks m, MonadReader DashBoardEnv m) => LWWRefKey 'HBS2Basic -> m ()
|
||||
addRepoIndexJob lww = do
|
||||
|
||||
e <- ask
|
||||
let wip = _repoCommitIndexWIP e
|
||||
|
||||
n <- atomically do
|
||||
modifyTVar wip (HM.insertWith (+) (coerce lww) 1)
|
||||
readTVar wip <&> HM.lookup (coerce lww) <&> fromMaybe 0
|
||||
|
||||
when ( n < 2 ) do
|
||||
addJob $ withDashBoardEnv e do
|
||||
buildCommitTreeIndex (coerce lww)
|
||||
`finally` do
|
||||
atomically do
|
||||
modifyTVar wip (HM.adjust pred (coerce lww))
|
||||
|
||||
updateFixmeFor :: ( MonadUnliftIO m
|
||||
, MonadReader DashBoardEnv m
|
||||
)
|
||||
=> RepoLww
|
||||
-> MyRefChan
|
||||
-> m ()
|
||||
updateFixmeFor (RepoLww lw) f = do
|
||||
p <- fixmeDataPath f
|
||||
debug $ red "UPDATE-FIXME-FOR" <+> pretty (AsBase58 lw) <+> pretty (AsBase58 f) <+> pretty p
|
||||
|
||||
let rcp = show $ pretty (AsBase58 f)
|
||||
|
||||
mkdir p
|
||||
|
||||
let cmdStr = [qc|fixme-new refchan {rcp} and fixme:refchan:import|]
|
||||
let cmd = shell cmdStr & setWorkingDir p
|
||||
|
||||
debug $ "run fixme for:" <+> pretty rcp <+> pretty cmdStr
|
||||
|
||||
void $ runProcess cmd
|
||||
|
||||
|
||||
updateIndexFromPeer :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m ()
|
||||
updateIndexFromPeer = do
|
||||
debug "updateIndexFromPeer"
|
||||
|
||||
peer <- asks _peerAPI
|
||||
reflog <- asks _refLogAPI
|
||||
lwwAPI <- asks _lwwRefAPI
|
||||
sto <- asks _sto
|
||||
|
||||
|
||||
polls <- callRpcWaitMay @RpcPollList2 (TimeoutSec 1) peer (Just "lwwref", Nothing)
|
||||
<&> join . maybeToList
|
||||
<&> fmap (LWWRefKey @HBS2Basic . view _1)
|
||||
|
||||
repos <- S.toList_ $ forM_ polls $ \r -> void $ runMaybeT do
|
||||
|
||||
lwval <- liftIO (callRpcWaitMay @RpcLWWRefGet (seconds 1) lwwAPI r)
|
||||
>>= toMPlus >>= toMPlus
|
||||
|
||||
(lw,blk) <- readLWWBlock sto r >>= toMPlus
|
||||
let rk = lwwRefLogPubKey blk
|
||||
|
||||
lift $ S.yield (r,lwval,RefLogKey @'HBS2Basic rk,blk)
|
||||
|
||||
|
||||
for_ repos $ \(lw,wv,rk,LWWBlockData{..}) -> do
|
||||
|
||||
mhead <- callRpcWaitMay @RpcRefLogGet (TimeoutSec 1) reflog (coerce rk)
|
||||
<&> join
|
||||
|
||||
for_ mhead $ \mh -> do
|
||||
|
||||
txs <- S.toList_ $ do
|
||||
walkMerkle @[HashRef] (fromHashRef mh) (getBlock sto) $ \case
|
||||
Left{} -> do
|
||||
pure ()
|
||||
|
||||
Right hxs -> do
|
||||
for_ hxs $ \htx -> void $ runMaybeT do
|
||||
|
||||
done <- lift $ withState $ isProcessed (HashRef $ hashObject @HbSync (serialise (lw,htx)))
|
||||
|
||||
guard (not done)
|
||||
|
||||
getBlock sto (fromHashRef htx) >>= toMPlus
|
||||
<&> deserialiseOrFail @(RefLogUpdate L4Proto)
|
||||
>>= toMPlus
|
||||
>>= unpackTx
|
||||
>>= \(n,h,blk) -> lift (S.yield (n,htx,blk))
|
||||
|
||||
|
||||
headz <- S.toList_ do
|
||||
for_ txs $ \(n,tx,blk) -> void $ runMaybeT do
|
||||
(rhh, rhead) <- readRepoHeadFromTx sto tx >>= toMPlus
|
||||
debug $ yellow "found repo head" <+> pretty rhh <+> pretty "for" <+> pretty lw
|
||||
(man, _) <- parseManifest rhead
|
||||
let fme = headMay [ x | FixmeRefChanP x <- man ]
|
||||
lift $ S.yield (lw, RepoHeadTx tx, RepoHeadRef rhh, rhead, fme)
|
||||
|
||||
withState $ transactional do
|
||||
-- withState do
|
||||
for_ headz $ \(l, tx, rh, rhead, fme) -> do
|
||||
let rlwwseq = RepoLwwSeq (fromIntegral $ lwwSeq wv)
|
||||
insertRepoHead l rlwwseq (RepoRefLog rk) tx rh rhead
|
||||
|
||||
insertProcessed (HashRef $ hashObject @HbSync (serialise (l,coerce @_ @HashRef tx)))
|
||||
|
||||
for_ fme $ \f -> do
|
||||
insertRepoFixme l rlwwseq f
|
||||
|
||||
-- WTF?
|
||||
env <- ask
|
||||
buildCommitTreeIndex (coerce lw)
|
||||
|
||||
fxe <- selectRepoFixme
|
||||
|
||||
for_ fxe $ \(r,f) -> do
|
||||
allowed <- checkFixmeAllowed r
|
||||
when allowed do
|
||||
env <-ask
|
||||
addJob (withDashBoardEnv env $ updateFixmeFor r f)
|
||||
|
||||
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# Language UndecidableInstances #-}
|
||||
{-# Language AllowAmbiguousTypes #-}
|
||||
{-# Language TemplateHaskell #-}
|
||||
module HBS2.Git.DashBoard.Types
|
||||
( module HBS2.Git.DashBoard.Types
|
||||
, module HBS2.Git.Data.Tx.Index
|
||||
) where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
|
||||
import HBS2.Git.Data.Tx.Index
|
||||
|
||||
import HBS2.Net.Messaging.Unix
|
||||
|
||||
import DBPipe.SQLite
|
||||
|
||||
import HBS2.System.Dir
|
||||
|
||||
import System.FilePath
|
||||
|
||||
import Data.Word
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Data.Text qualified as Text
|
||||
|
||||
type MyRefChan = RefChanId L4Proto
|
||||
type MyRefLogKey = RefLogKey 'HBS2Basic
|
||||
|
||||
data HttpPortOpt
|
||||
|
||||
data DevelopAssetsOpt
|
||||
|
||||
instance HasCfgKey HttpPortOpt a where
|
||||
key = "port"
|
||||
|
||||
|
||||
instance HasCfgKey DevelopAssetsOpt a where
|
||||
key = "develop-assets"
|
||||
|
||||
data RunDashBoardOpts = RunDashBoardOpts
|
||||
{ configPath :: Maybe FilePath }
|
||||
|
||||
instance Monoid RunDashBoardOpts where
|
||||
mempty = RunDashBoardOpts Nothing
|
||||
|
||||
instance Semigroup RunDashBoardOpts where
|
||||
(<>) _ b = RunDashBoardOpts { configPath = configPath b }
|
||||
|
||||
|
||||
data DashBoardEnv =
|
||||
DashBoardEnv
|
||||
{ _peerAPI :: ServiceCaller PeerAPI UNIX
|
||||
, _refLogAPI :: ServiceCaller RefLogAPI UNIX
|
||||
, _refChanAPI :: ServiceCaller RefChanAPI UNIX
|
||||
, _lwwRefAPI :: ServiceCaller LWWRefAPI UNIX
|
||||
, _sto :: AnyStorage
|
||||
, _dataDir :: FilePath
|
||||
, _db :: TVar (Maybe DBPipeEnv)
|
||||
, _pipeline :: TQueue (IO ())
|
||||
, _dashBoardHttpPort :: TVar (Maybe Word16)
|
||||
, _dashBoardDevAssets :: TVar (Maybe FilePath)
|
||||
, _dashBoardBaseUrl :: TVar (Maybe Text)
|
||||
, _dashBoardIndexIgnoreCaches :: TVar Bool
|
||||
, _repoCommitIndexWIP :: TVar (HashMap (LWWRefKey 'HBS2Basic) Int)
|
||||
}
|
||||
|
||||
makeLenses 'DashBoardEnv
|
||||
|
||||
repoDataPath :: (DashBoardPerks m, MonadReader DashBoardEnv m) => LWWRefKey 'HBS2Basic -> m FilePath
|
||||
repoDataPath lw = asks _dataDir <&> (</> (show $ pretty lw)) >>= canonicalizePath
|
||||
|
||||
fixmeDataPath :: (DashBoardPerks m, MonadReader DashBoardEnv m) => MyRefChan -> m FilePath
|
||||
fixmeDataPath rchan = asks _dataDir <&> (</> (show $ "fixme-" <> pretty (AsBase58 rchan))) >>= canonicalizePath
|
||||
|
||||
type DashBoardPerks m = MonadUnliftIO m
|
||||
|
||||
newtype DashBoardM m a = DashBoardM { fromDashBoardM :: ReaderT DashBoardEnv m a }
|
||||
deriving newtype
|
||||
( Applicative
|
||||
, Functor
|
||||
, Monad
|
||||
, MonadIO
|
||||
, MonadUnliftIO
|
||||
, MonadTrans
|
||||
, MonadReader DashBoardEnv
|
||||
)
|
||||
|
||||
newDashBoardEnv :: MonadIO m
|
||||
=> FilePath
|
||||
-> ServiceCaller PeerAPI UNIX
|
||||
-> ServiceCaller RefLogAPI UNIX
|
||||
-> ServiceCaller RefChanAPI UNIX
|
||||
-> ServiceCaller LWWRefAPI UNIX
|
||||
-> AnyStorage
|
||||
-> m DashBoardEnv
|
||||
newDashBoardEnv ddir peer rlog rchan lww sto = do
|
||||
DashBoardEnv peer rlog rchan lww sto ddir
|
||||
<$> newTVarIO mzero
|
||||
<*> newTQueueIO
|
||||
<*> newTVarIO (Just 8911)
|
||||
<*> newTVarIO Nothing
|
||||
<*> newTVarIO Nothing
|
||||
<*> newTVarIO False
|
||||
<*> newTVarIO mempty
|
||||
|
||||
getHttpPortNumber :: (MonadIO m, MonadReader DashBoardEnv m, Integral a) => m a
|
||||
getHttpPortNumber = do
|
||||
asks _dashBoardHttpPort
|
||||
>>= readTVarIO
|
||||
<&> fromIntegral . fromMaybe 8911
|
||||
|
||||
getDevAssets :: (MonadIO m, MonadReader DashBoardEnv m, Integral a) => m (Maybe FilePath)
|
||||
getDevAssets = do
|
||||
asks _dashBoardDevAssets
|
||||
>>= readTVarIO
|
||||
|
||||
|
||||
getIgnoreCaches :: (MonadIO m, MonadReader DashBoardEnv m, Integral a) => m Bool
|
||||
getIgnoreCaches = do
|
||||
asks _dashBoardIndexIgnoreCaches
|
||||
>>= readTVarIO
|
||||
|
||||
asksBaseUrl :: (MonadIO m, MonadReader DashBoardEnv m) => (Text -> m a) -> m a
|
||||
asksBaseUrl thingInside = do
|
||||
mUrl <- readTVarIO =<< asks _dashBoardBaseUrl
|
||||
thingInside (fromMaybe (Text.pack "") mUrl)
|
||||
|
||||
withDashBoardEnv :: Monad m => DashBoardEnv -> DashBoardM m a -> m a
|
||||
withDashBoardEnv env m = runReaderT (fromDashBoardM m) env
|
||||
|
||||
data StateFSM m a =
|
||||
S0
|
||||
| SConnect
|
||||
|
||||
withState :: forall m a . (MonadIO m, MonadReader DashBoardEnv m) => DBPipeM m a -> m a
|
||||
withState f = do
|
||||
|
||||
dbFile <- asks _dataDir <&> (</> "state.db")
|
||||
tdb <- asks _db
|
||||
|
||||
flip fix S0 $ \next -> \case
|
||||
|
||||
SConnect -> do
|
||||
notice $ yellow "connecting to db"
|
||||
dbe <- liftIO $ try @_ @SomeException (newDBPipeEnv (dbPipeOptsDef {dbPipeBatchTime = 1}) dbFile)
|
||||
|
||||
case dbe of
|
||||
Right e -> do
|
||||
atomically $ writeTVar tdb (Just e)
|
||||
next S0
|
||||
|
||||
Left what -> do
|
||||
err $ viaShow what
|
||||
pause @Seconds 1
|
||||
next SConnect
|
||||
|
||||
S0 -> do
|
||||
dbe <- readTVarIO tdb
|
||||
|
||||
case dbe of
|
||||
Just d -> withDB d f
|
||||
Nothing -> next SConnect
|
||||
|
||||
|
||||
addJob :: (DashBoardPerks m, MonadReader DashBoardEnv m) => IO () -> m ()
|
||||
addJob f = do
|
||||
q <- asks _pipeline
|
||||
atomically $ writeTQueue q f
|
||||
|
||||
|
||||
|
||||
hbs2_git_dashboard :: FilePath
|
||||
hbs2_git_dashboard = "hbs2-git-dashboard"
|
|
@ -1,102 +0,0 @@
|
|||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# LANGUAGE CPP #-}
|
||||
module HBS2.Git.Web.Html.Fixme where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Fixme as Fixme
|
||||
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
|
||||
import Data.Map qualified as Map
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
import Lucid.Htmx
|
||||
|
||||
import Data.Word
|
||||
import Data.List qualified as List
|
||||
|
||||
import Web.Scotty.Trans as Scotty
|
||||
|
||||
|
||||
#if __GLASGOW_HASKELL__ < 906
|
||||
import Control.Applicative -- add liftA2 into scope
|
||||
#endif
|
||||
|
||||
instance ToHtml (H FixmeKey) where
|
||||
toHtmlRaw (H k) = toHtmlRaw $ take 10 $ show $ pretty k
|
||||
toHtml (H k) = toHtml $ take 10 $ show $ pretty k
|
||||
|
||||
instance ToHtml (H FixmeTag) where
|
||||
toHtmlRaw (H k) = toHtmlRaw $ coerce @_ @Text k
|
||||
toHtml (H k) = toHtml $ coerce @_ @Text k
|
||||
|
||||
instance ToHtml (H FixmeTitle) where
|
||||
toHtmlRaw (H k) = toHtmlRaw $ coerce @_ @Text k
|
||||
toHtml (H k) = toHtml $ coerce @_ @Text k
|
||||
|
||||
repoFixme :: ( MonadReader DashBoardEnv m
|
||||
, DashBoardPerks m
|
||||
, HasLimit q
|
||||
, HasPredicate q
|
||||
, q ~ FromParams 'FixmeDomain [Param]
|
||||
)
|
||||
=> q
|
||||
-> LWWRefKey HBS2Basic
|
||||
-> HtmlT m ()
|
||||
|
||||
repoFixme q@(FromParams p') lww = asksBaseUrl $ withBaseUrl do
|
||||
|
||||
let p = Map.fromList p'
|
||||
|
||||
now <- liftIO $ getPOSIXTime <&> round
|
||||
|
||||
debug $ blue "repoFixme" <+> "LIMITS" <+> viaShow (limit q)
|
||||
|
||||
let offset = maybe 0 fst (limit q)
|
||||
|
||||
fme <- lift $ listFixme (RepoLww lww) (Reversed q)
|
||||
|
||||
for_ fme $ \fixme -> do
|
||||
tr_ [class_ "commit-brief-title"] $ do
|
||||
td_ [class_ "mono", width_ "10"] do
|
||||
a_ [ href_ (toBaseURL (IssuePage (RepoLww lww) (fixmeKey fixme)))
|
||||
] $ toHtml (H $ fixmeKey fixme)
|
||||
td_ [width_ "10"] do
|
||||
strong_ [] $ toHtml (H $ fixmeTag fixme)
|
||||
td_ [] do
|
||||
toHtml (H $ fixmeTitle fixme)
|
||||
tr_ [class_ "commit-brief-details"] $ do
|
||||
td_ [colspan_ "3"] do
|
||||
let mco = fixmeGet "commit-time" fixme & pretty & show & readMay @Word64
|
||||
let mw = fixmeGet "workflow" fixme <&> coerce @_ @Text
|
||||
let cla = fixmeGet "class" fixme <&> coerce @_ @Text
|
||||
let mn = liftA2 (-) (fixmeEnd fixme) (fixmeStart fixme)
|
||||
|
||||
small_ do
|
||||
for_ mw $ \w -> do
|
||||
span_ [] (toHtml $ show $ brackets $ pretty w)
|
||||
" "
|
||||
|
||||
for_ mco $ \co ->
|
||||
span_ [] $ toHtml $ show $ brackets ("commited" <+> pretty (agePure co now))
|
||||
|
||||
for_ cla $ \c ->
|
||||
span_ [] $ toHtml $ show $ brackets (pretty c)
|
||||
|
||||
for_ mn $ \n -> do
|
||||
when (n > 0) do
|
||||
span_ [] $ toHtml $ show $ brackets ("text:" <+> pretty n)
|
||||
|
||||
|
||||
unless (List.null fme) do
|
||||
tr_ [ class_ "commit-brief-last"
|
||||
, hxGet_ (toBaseURL (Paged (offset + fromIntegral fixmePageSize) (RepoFixmeHtmx p (RepoLww lww))))
|
||||
, hxTrigger_ "revealed"
|
||||
, hxSwap_ "afterend"
|
||||
] do
|
||||
td_ [colspan_ "3"] mempty
|
||||
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
{-# LANGUAGE CPP #-}
|
||||
|
||||
module HBS2.Git.Web.Html.Issue (issuePage) where
|
||||
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Fixme as Fixme
|
||||
|
||||
import HBS2.OrDie
|
||||
|
||||
import HBS2.Git.Web.Assets
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
import HBS2.Git.Web.Html.Root
|
||||
import HBS2.Git.Web.Html.Markdown
|
||||
import HBS2.Git.Web.Html.Fixme()
|
||||
import HBS2.Git.Web.Html.Parts.Blob
|
||||
|
||||
import Data.Text qualified as Text
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
|
||||
#if __GLASGOW_HASKELL__ < 906
|
||||
import Control.Applicative -- add liftA2 into scope
|
||||
#endif
|
||||
|
||||
data IssueOptionalArg w t = IssueOptionalArg w t
|
||||
|
||||
issueOptionalArg :: Fixme -> FixmeAttrName -> IssueOptionalArg Fixme FixmeAttrName
|
||||
issueOptionalArg = IssueOptionalArg
|
||||
|
||||
instance ToHtml (IssueOptionalArg Fixme FixmeAttrName) where
|
||||
toHtml (IssueOptionalArg fxm n) = do
|
||||
for_ (fixmeGet n fxm) $ \t -> do
|
||||
tr_ do
|
||||
td_ [class_ "whitespace-nowrap"] $ strong_ (toHtml $ show $ pretty n)
|
||||
td_ [class_ "w-full"] (toHtml $ show $ pretty t)
|
||||
|
||||
toHtmlRaw = toHtml
|
||||
|
||||
issuePage :: (MonadIO m, DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> RepoLww
|
||||
-> FixmeKey
|
||||
-> HtmlT m ()
|
||||
|
||||
issuePage repo@(RepoLww lww) f = asksBaseUrl $ withBaseUrl $ rootPage do
|
||||
|
||||
ti@TopInfoBlock{} <- lift $ getTopInfoBlock (coerce repo)
|
||||
|
||||
fxm <- lift (getFixme repo f)
|
||||
>>= orThrow (itemNotFound f)
|
||||
|
||||
let txt = fixmePlain fxm & fmap coerce & Text.intercalate "\n"
|
||||
|
||||
let mbFile = fixmeGet "file" fxm
|
||||
|
||||
mbBlob <- runMaybeT do
|
||||
blobHashText <- fixmeGet "blob" fxm & toMPlus
|
||||
debug $ red "BLOB HASH TEXT" <+> pretty blobHashText
|
||||
hash <- coerce blobHashText
|
||||
& Text.unpack
|
||||
& fromStringMay @GitHash
|
||||
& toMPlus
|
||||
debug $ red "BLOB" <+> pretty hash
|
||||
lift (lift $ selectBlobInfo (BlobHash hash))
|
||||
>>= toMPlus
|
||||
|
||||
debug $ "BLOB INFO" <> line <> pretty (fmap blobHash mbBlob)
|
||||
|
||||
main_ [class_ "container-fluid"] do
|
||||
div_ [class_ "wrapper"] do
|
||||
aside_ [class_ "sidebar"] do
|
||||
|
||||
-- issuesSidebar (coerce repo) ti mempty
|
||||
repoTopInfoBlock (coerce repo) ti
|
||||
|
||||
div_ [class_ "content"] $ do
|
||||
|
||||
nav_ [class_ "mb-1"] do
|
||||
|
||||
div_ do
|
||||
small_ do
|
||||
a_ [ href_ (toBaseURL (RepoPage IssuesTab lww))
|
||||
] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconArrowUturnLeft
|
||||
span_ [] "back to issues"
|
||||
|
||||
article_ [class_ "issue-info-card"] do
|
||||
header_ do
|
||||
h5_ do
|
||||
toHtml (coerce @_ @Text $ fixmeTag fxm)
|
||||
" "
|
||||
span_ [class_ "font-normal"] do
|
||||
let fkKey = coerce @_ @Text $ fixmeKey fxm
|
||||
span_ [ class_ "issue-id secondary"
|
||||
, data_ "tooltip" "Copy"
|
||||
, onClickCopyText $ Text.take 10 fkKey
|
||||
] $ toHtml (H $ fixmeKey fxm)
|
||||
" "
|
||||
toHtml (coerce @_ @Text $ fixmeTitle fxm)
|
||||
|
||||
div_ [class_ "overflow-x-auto"] $ table_ [class_ "issue-info-table mb-0"] do
|
||||
|
||||
toHtml (issueOptionalArg fxm "workflow")
|
||||
toHtml (issueOptionalArg fxm "class")
|
||||
toHtml (issueOptionalArg fxm "assigned")
|
||||
toHtml (issueOptionalArg fxm "scope")
|
||||
toHtml (issueOptionalArg fxm "committer-name")
|
||||
toHtml (issueOptionalArg fxm "commit")
|
||||
|
||||
|
||||
maybe1 mbFile none $ \file -> do
|
||||
tr_ do
|
||||
th_ $ strong_ [] $ "file"
|
||||
|
||||
case mbBlob of
|
||||
Nothing -> do
|
||||
td_ do
|
||||
toHtml $ show $ pretty file
|
||||
Just (BlobInfo{}) -> do
|
||||
td_ do
|
||||
a_ [ href_ "#"
|
||||
, hyper_ "on click toggle .hidden on #issue-blob"
|
||||
] do
|
||||
toHtml $ show $ pretty file
|
||||
|
||||
-- toHtml (issueOptionalArg fxm "file")
|
||||
|
||||
section_ [class_ "lim-text"] do
|
||||
toHtmlRaw $ renderMarkdown txt
|
||||
|
||||
let s0 = fixmeStart fxm
|
||||
let e0 = fixmeEnd fxm
|
||||
let n = liftA2 (-) e0 s0 & fromMaybe 0
|
||||
|
||||
let hide = if n > 3 then "hidden" else ""
|
||||
|
||||
section_ [id_ "issue-blob", class_ hide ] $ void $ runMaybeT do
|
||||
blob <- toMPlus mbBlob
|
||||
s <- s0 & toMPlus <&> fromIntegral
|
||||
e <- e0 & toMPlus <&> fromIntegral
|
||||
|
||||
let before = max 0 (s - 2)
|
||||
let seize = max 1 (e - s + 100)
|
||||
|
||||
debug $ "PREPROCESS BLOB" <+> pretty before <+> pretty seize
|
||||
|
||||
lift $ doRenderBlob' (pure mempty) (trim before seize) lww blob
|
||||
|
||||
where
|
||||
trim before seize txt =
|
||||
Text.lines txt & drop before & take seize & Text.unlines
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
module HBS2.Git.Web.Html.Markdown where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import Data.Text qualified as Text
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
|
||||
import Text.Pandoc hiding (getPOSIXTime)
|
||||
|
||||
markdownToHtml :: Text -> Either PandocError String
|
||||
markdownToHtml markdown = runPure $ do
|
||||
doc <- readMarkdown def {readerExtensions = pandocExtensions} markdown
|
||||
html <- writeHtml5String def {writerExtensions = pandocExtensions} doc
|
||||
return $ Text.unpack html
|
||||
|
||||
renderMarkdown' :: Text -> Text
|
||||
renderMarkdown' markdown = case markdownToHtml markdown of
|
||||
Left{} -> markdown
|
||||
Right html -> Text.pack html
|
||||
|
||||
renderMarkdown :: Text -> Html ()
|
||||
renderMarkdown markdown = case markdownToHtml markdown of
|
||||
Left{} -> blockquote_ (toHtml markdown)
|
||||
Right html -> toHtmlRaw $ Text.pack html
|
|
@ -1,79 +0,0 @@
|
|||
module HBS2.Git.Web.Html.Parts.Blob where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Types
|
||||
|
||||
import HBS2.Git.Web.Html.Markdown
|
||||
|
||||
import Data.ByteString.Lazy qualified as LBS
|
||||
import Data.Text.Encoding qualified as Text
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
|
||||
import Skylighting qualified as Sky
|
||||
import Skylighting.Tokenizer
|
||||
import Skylighting.Format.HTML.Lucid as Lucid
|
||||
|
||||
import Control.Applicative
|
||||
|
||||
{-HLINT ignore "Functor law"-}
|
||||
|
||||
|
||||
doRenderBlob :: (MonadReader DashBoardEnv m, MonadUnliftIO m)
|
||||
=> (Text -> HtmlT m ())
|
||||
-> LWWRefKey HBS2Basic
|
||||
-> BlobInfo
|
||||
-> HtmlT m ()
|
||||
|
||||
doRenderBlob fallback = doRenderBlob' fallback id
|
||||
|
||||
doRenderBlob' :: (MonadReader DashBoardEnv m, MonadUnliftIO m)
|
||||
=> (Text -> HtmlT m ())
|
||||
-> (Text -> Text)
|
||||
-> LWWRefKey HBS2Basic
|
||||
-> BlobInfo
|
||||
-> HtmlT m ()
|
||||
|
||||
doRenderBlob' fallback preprocess lww BlobInfo{..} = do
|
||||
fromMaybe mempty <$> runMaybeT do
|
||||
|
||||
guard (blobSize < 10485760)
|
||||
|
||||
let fn = blobName & coerce
|
||||
let syntaxMap = Sky.defaultSyntaxMap
|
||||
|
||||
syn <- ( Sky.syntaxesByFilename syntaxMap fn
|
||||
& headMay
|
||||
) <|> Sky.syntaxByName syntaxMap "default"
|
||||
& toMPlus
|
||||
|
||||
lift do
|
||||
|
||||
txt <- lift (readBlob lww blobHash)
|
||||
<&> LBS.toStrict
|
||||
<&> Text.decodeUtf8
|
||||
|
||||
case blobSyn of
|
||||
BlobSyn (Just "markdown") -> do
|
||||
|
||||
div_ [class_ "lim-text"] do
|
||||
toHtmlRaw (renderMarkdown' txt)
|
||||
|
||||
_ -> do
|
||||
|
||||
txt <- lift (readBlob lww blobHash)
|
||||
<&> LBS.toStrict
|
||||
<&> Text.decodeUtf8
|
||||
<&> preprocess
|
||||
|
||||
let config = TokenizerConfig { traceOutput = False, syntaxMap = syntaxMap }
|
||||
|
||||
case tokenize config syn txt of
|
||||
Left _ -> fallback txt
|
||||
Right tokens -> do
|
||||
let fo = Sky.defaultFormatOpts { Sky.numberLines = False, Sky.ansiColorLevel = Sky.ANSI256Color }
|
||||
let code = renderText (Lucid.formatHtmlBlock fo tokens)
|
||||
toHtmlRaw code
|
||||
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
module HBS2.Git.Web.Html.Parts.Issues.Sidebar where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Fixme as Fixme
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
import HBS2.Git.Web.Html.Parts.TopInfoBlock
|
||||
|
||||
import Data.Map qualified as Map
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
import Lucid.Htmx
|
||||
|
||||
|
||||
|
||||
issuesSidebar :: (MonadIO m, DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> TopInfoBlock
|
||||
-> [(Text,Text)]
|
||||
-> HtmlT m ()
|
||||
issuesSidebar lww topInfoBlock p' = asksBaseUrl $ withBaseUrl do
|
||||
|
||||
let p = Map.fromList p'
|
||||
|
||||
tot <- lift $ countFixme (RepoLww lww)
|
||||
fmw <- lift $ countFixmeByAttribute (RepoLww lww) "workflow"
|
||||
fmt <- lift $ countFixmeByAttribute (RepoLww lww) "fixme-tag"
|
||||
ass <- lift $ countFixmeByAttribute (RepoLww lww) "assigned"
|
||||
cla <- lift $ countFixmeByAttribute (RepoLww lww) "class"
|
||||
|
||||
repoTopInfoBlock lww topInfoBlock
|
||||
|
||||
div_ [class_ "info-block" ] do
|
||||
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Tag"
|
||||
|
||||
-- TODO: make-this-block-properly
|
||||
|
||||
ul_ do
|
||||
for_ fmt $ \(s,n) -> do
|
||||
li_ [] $ small_ [] do
|
||||
a_ [ class_ "secondary"
|
||||
, hxGet_ (toBaseURL (Paged 0 (RepoFixmeHtmx (Map.insert "fixme-tag" (coerce s) p) (RepoLww lww))))
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
] do
|
||||
span_ [style_ "display: inline-block; width: 4ch; text-align: right; padding-right: 0.5em;"] $
|
||||
toHtml $ show $ pretty n
|
||||
|
||||
span_ [] $ toHtml $ show $ pretty s
|
||||
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Status"
|
||||
|
||||
ul_ do
|
||||
|
||||
li_ [] $ small_ [] do
|
||||
a_ [ class_ "secondary"
|
||||
, hxGet_ (toBaseURL (Paged 0 (RepoFixmeHtmx (Map.delete "workflow" p) (RepoLww lww))))
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
] do
|
||||
span_ [style_ "display: inline-block; width: 4ch; text-align: right; padding-right: 0.5em;"] $
|
||||
toHtml $ show $ pretty (fromMaybe 0 tot)
|
||||
|
||||
span_ [] $ toHtml $ show $ pretty "[all]"
|
||||
|
||||
for_ fmw $ \(s,n) -> do
|
||||
li_ [] $ small_ [] do
|
||||
a_ [ class_ "secondary"
|
||||
, hxGet_ (toBaseURL (Paged 0 (RepoFixmeHtmx (Map.insert "workflow" (coerce s) p) (RepoLww lww))))
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
] do
|
||||
span_ [style_ "display: inline-block; width: 4ch; text-align: right; padding-right: 0.5em;"] $
|
||||
toHtml $ show $ pretty n
|
||||
|
||||
span_ [] $ toHtml $ show $ pretty s
|
||||
|
||||
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Assigned"
|
||||
|
||||
for_ ass $ \(s,n) -> do
|
||||
li_ [] $ small_ [] do
|
||||
a_ [ class_ "secondary"
|
||||
, hxGet_ (toBaseURL (Paged 0 (RepoFixmeHtmx (Map.insert "assigned" (coerce s) p) (RepoLww lww))))
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
] do
|
||||
span_ [style_ "display: inline-block; width: 4ch; text-align: right; padding-right: 0.5em;"] $
|
||||
toHtml $ show $ pretty n
|
||||
|
||||
span_ [] $ toHtml $ show $ pretty s
|
||||
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Class"
|
||||
|
||||
for_ cla $ \(s,n) -> do
|
||||
li_ [] $ small_ [] do
|
||||
a_ [ class_ "secondary"
|
||||
, hxGet_ (toBaseURL (Paged 0 (RepoFixmeHtmx (Map.insert "class" (coerce s) p) (RepoLww lww))))
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
] do
|
||||
span_ [style_ "display: inline-block; width: 4ch; text-align: right; padding-right: 0.5em;"] $
|
||||
toHtml $ show $ pretty n
|
||||
|
||||
span_ [] $ toHtml $ show $ pretty s
|
||||
|
||||
pure ()
|
|
@ -1,152 +0,0 @@
|
|||
module HBS2.Git.Web.Html.Parts.TopInfoBlock where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Manifest
|
||||
import HBS2.Git.DashBoard.Fixme as Fixme
|
||||
|
||||
import HBS2.OrDie
|
||||
|
||||
import HBS2.Git.Data.Tx.Git
|
||||
import HBS2.Git.Web.Assets
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
|
||||
import Data.Text qualified as Text
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
|
||||
data TopInfoBlock =
|
||||
TopInfoBlock
|
||||
{ author :: Maybe Text
|
||||
, public :: Maybe Text
|
||||
, forksNum :: RepoForks
|
||||
, commitsNum :: RepoCommitsNum
|
||||
, manifest :: Text
|
||||
, fixme :: Maybe MyRefChan
|
||||
, fixmeCnt :: Int
|
||||
, pinned :: [(Text, Syntax C)]
|
||||
, repoHeadRef :: RepoHeadRef
|
||||
, repoHead :: Maybe RepoHead
|
||||
, repoName :: RepoName
|
||||
}
|
||||
|
||||
repoTopInfoBlock :: (MonadIO m, DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> TopInfoBlock
|
||||
-> HtmlT m ()
|
||||
|
||||
repoTopInfoBlock lww TopInfoBlock{..} = asksBaseUrl $ withBaseUrl do
|
||||
|
||||
div_ [class_ "info-block" ] do
|
||||
let url = toBaseURL (RepoPage (CommitsTab Nothing) lww)
|
||||
let txt = toHtml (ShortRef lww)
|
||||
a_ [href_ url, class_ "secondary"] txt
|
||||
|
||||
div_ [class_ "info-block" ] do
|
||||
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "About"
|
||||
ul_ [class_ "mb-0"] do
|
||||
for_ author $ \a -> do
|
||||
li_ $ small_ do
|
||||
"Author: "
|
||||
toHtml a
|
||||
|
||||
for_ public $ \p -> do
|
||||
li_ $ small_ do
|
||||
"Public: "
|
||||
toHtml p
|
||||
|
||||
when (Text.length manifest > 100) do
|
||||
li_ $ small_ do
|
||||
a_ [class_ "secondary", href_ (toBaseURL (RepoPage ManifestTab lww))] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconLicense
|
||||
"Manifest"
|
||||
|
||||
for_ fixme $ \_ -> do
|
||||
li_ $ small_ do
|
||||
a_ [ class_ "secondary"
|
||||
, href_ (toBaseURL (RepoPage IssuesTab lww)) ] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconFixme
|
||||
toHtml $ show fixmeCnt
|
||||
" Issues"
|
||||
|
||||
when (forksNum > 0) do
|
||||
li_ $ small_ do
|
||||
a_ [class_ "secondary"
|
||||
, href_ (toBaseURL (RepoPage ForksTab lww))
|
||||
] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconGitFork
|
||||
toHtml $ show forksNum
|
||||
" forks"
|
||||
|
||||
li_ $ small_ do
|
||||
a_ [class_ "secondary"
|
||||
, href_ (toBaseURL (RepoPage (CommitsTab Nothing) lww))
|
||||
] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconGitCommit
|
||||
toHtml $ show commitsNum
|
||||
" commits"
|
||||
|
||||
for_ pinned $ \(_,ref) -> do
|
||||
case ref of
|
||||
PinnedRefBlob s n hash -> small_ do
|
||||
li_ $ a_ [class_ "secondary"
|
||||
, href_ (toBaseURL (RepoPage (PinnedTab (Just (s,n,hash))) lww))
|
||||
] do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconPinned
|
||||
toHtml (Text.take 12 n)
|
||||
" "
|
||||
toHtml $ ShortRef hash
|
||||
|
||||
parsedManifest :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoListItem -> m ([Syntax C], Text)
|
||||
parsedManifest RepoListItem{..} = do
|
||||
|
||||
sto <- asks _sto
|
||||
mhead <- readRepoHeadFromTx sto (coerce rlRepoTx)
|
||||
|
||||
case mhead of
|
||||
Just x -> parseManifest (snd x)
|
||||
Nothing -> pure (mempty, coerce rlRepoBrief)
|
||||
|
||||
|
||||
getTopInfoBlock :: ( MonadUnliftIO m, MonadIO m
|
||||
, MonadReader DashBoardEnv m
|
||||
)
|
||||
=> LWWRefKey HBS2Basic -> m TopInfoBlock
|
||||
getTopInfoBlock lww = do
|
||||
|
||||
debug $ red "getTopInfoBlock"
|
||||
|
||||
it@RepoListItem{..} <- (selectRepoList ( mempty
|
||||
& set repoListByLww (Just lww)
|
||||
& set repoListLimit (Just 1))
|
||||
<&> listToMaybe
|
||||
) >>= orThrow (itemNotFound lww)
|
||||
|
||||
sto <- asks _sto
|
||||
mhead <- readRepoHeadFromTx sto (coerce rlRepoTx)
|
||||
|
||||
let repoHead = snd <$> mhead
|
||||
|
||||
(meta, manifest) <- parsedManifest it
|
||||
|
||||
let author = headMay [ s | ListVal [ SymbolVal "author:", LitStrVal s ] <- meta ]
|
||||
let public = headMay [ s | ListVal [ SymbolVal "public:", SymbolVal (Id s) ] <- meta ]
|
||||
let pinned = [ (name,r) | ListVal [ SymbolVal "pinned:", r@(PinnedRefBlob _ name _) ] <- meta ] & take 5
|
||||
|
||||
allowed <- checkFixmeAllowed (RepoLww lww)
|
||||
let fixme = headMay [ x | allowed, FixmeRefChanP x <- meta ]
|
||||
|
||||
fixmeCnt <- if allowed then
|
||||
Fixme.countFixme (RepoLww lww) <&> fromMaybe 0
|
||||
else
|
||||
pure 0
|
||||
|
||||
let forksNum = rlRepoForks
|
||||
let commitsNum = rlRepoCommits
|
||||
let repoHeadRef = rlRepoHead
|
||||
let repoName = rlRepoName
|
||||
|
||||
pure $ TopInfoBlock{..}
|
|
@ -1,593 +0,0 @@
|
|||
{-# Language MultiWayIf #-}
|
||||
module HBS2.Git.Web.Html.Repo where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.State.Commits
|
||||
import HBS2.Git.DashBoard.Manifest
|
||||
|
||||
import HBS2.OrDie
|
||||
|
||||
import HBS2.Git.Data.Tx.Git
|
||||
import HBS2.Git.Data.RepoHead
|
||||
import HBS2.Git.Web.Assets
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
import HBS2.Git.Web.Html.Root
|
||||
import HBS2.Git.Web.Html.Markdown
|
||||
import HBS2.Git.Web.Html.Parts.Issues.Sidebar
|
||||
import HBS2.Git.Web.Html.Parts.Blob
|
||||
|
||||
|
||||
import Data.Map qualified as Map
|
||||
import Data.Text qualified as Text
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
import Lucid.Htmx
|
||||
|
||||
import Skylighting qualified as Sky
|
||||
import Skylighting.Tokenizer
|
||||
import Skylighting.Format.HTML.Lucid as Lucid
|
||||
|
||||
import Data.Either
|
||||
import Data.List qualified as List
|
||||
import Data.List (sortOn)
|
||||
|
||||
import Streaming.Prelude qualified as S
|
||||
|
||||
isActiveTab :: RepoPageTabs -> RepoPageTabs -> Bool
|
||||
isActiveTab a b = case (a,b) of
|
||||
(CommitsTab{},CommitsTab{}) -> True
|
||||
(ManifestTab{},ManifestTab{}) -> True
|
||||
(TreeTab{},TreeTab{}) -> True
|
||||
_ -> False
|
||||
|
||||
|
||||
|
||||
repoPage :: (MonadIO m, DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> RepoPageTabs
|
||||
-> LWWRefKey 'HBS2Basic
|
||||
-> [(Text,Text)]
|
||||
-> HtmlT m ()
|
||||
|
||||
repoPage IssuesTab lww p' = asksBaseUrl $ withBaseUrl $ rootPage do
|
||||
|
||||
ti@TopInfoBlock{..} <- lift $ getTopInfoBlock lww
|
||||
|
||||
main_ [class_ "container-fluid"] do
|
||||
div_ [class_ "wrapper"] do
|
||||
aside_ [class_ "sidebar"] do
|
||||
|
||||
issuesSidebar lww ti p'
|
||||
|
||||
div_ [class_ "content"] $ do
|
||||
|
||||
section_ do
|
||||
h5_ $ toHtml (show $ "Issues ::" <+> pretty repoName)
|
||||
|
||||
form_ [role_ "search"] do
|
||||
input_ [name_ "search", type_ "search"]
|
||||
input_ [type_ "submit", value_ "Search"]
|
||||
|
||||
table_ [] do
|
||||
tbody_ [id_ "fixme-tab-data"] mempty
|
||||
|
||||
div_ [ id_ "repo-tab-data"
|
||||
, hxTrigger_ "load"
|
||||
, hxTarget_ "#fixme-tab-data"
|
||||
, hxGet_ (toBaseURL (RepoFixmeHtmx mempty (RepoLww lww)))
|
||||
] mempty
|
||||
|
||||
div_ [id_ "repo-tab-data-embedded"] mempty
|
||||
|
||||
|
||||
repoPage tab lww params = asksBaseUrl $ withBaseUrl $ rootPage do
|
||||
|
||||
sto <- asks _sto
|
||||
|
||||
topInfoBlock@TopInfoBlock{..} <- lift $ getTopInfoBlock lww
|
||||
|
||||
main_ [class_ "container-fluid"] do
|
||||
div_ [class_ "wrapper"] do
|
||||
aside_ [class_ "sidebar"] do
|
||||
|
||||
|
||||
repoTopInfoBlock lww topInfoBlock
|
||||
|
||||
for_ repoHead $ \rh -> do
|
||||
|
||||
let theHead = headMay [ v | (GitRef "HEAD", v) <- view repoHeadRefs rh ]
|
||||
|
||||
let checkHead v what | v == theHead = strong_ what
|
||||
| otherwise = what
|
||||
|
||||
div_ [class_ "info-block" ] do
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Heads"
|
||||
ul_ [class_ "mb-0"] $ do
|
||||
for_ (view repoHeadHeads rh) $ \(branch,v) -> do
|
||||
li_ $ small_ do
|
||||
a_ [class_ "secondary", href_ (toBaseURL (RepoPage (CommitsTab (Just v)) lww ))] do
|
||||
checkHead (Just v) $ toHtml branch
|
||||
|
||||
div_ [class_ "info-block" ] do
|
||||
summary_ [class_ "sidebar-title"] $ small_ $ strong_ "Tags"
|
||||
ul_ [class_ "mb-0"] $ do
|
||||
for_ (view repoHeadTags rh) $ \(tag,v) -> do
|
||||
li_ $ small_ do
|
||||
a_ [class_ "secondary", href_ (toBaseURL (RepoPage (CommitsTab (Just v)) lww ))] do
|
||||
checkHead (Just v) $ toHtml tag
|
||||
|
||||
div_ [class_ "content"] $ do
|
||||
|
||||
article_ [class_ "py-0"] $ nav_ [ariaLabel_ "breadcrumb", class_ "repo-menu"] $ ul_ do
|
||||
|
||||
let menuTabClasses isActive = if isActive then "tab contrast" else "tab"
|
||||
menuTab t misc name = li_ do
|
||||
a_ ([class_ $ menuTabClasses $ isActiveTab tab t] <> misc <> [tabClick]) do
|
||||
name
|
||||
|
||||
menuTab (CommitsTab Nothing)
|
||||
[ href_ "#"
|
||||
, hxGet_ (toBaseURL (RepoCommits lww))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] "commits"
|
||||
|
||||
menuTab (TreeTab Nothing)
|
||||
[ href_ "#"
|
||||
, hxGet_ (toBaseURL (RepoRefs lww))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] "tree"
|
||||
|
||||
section_ do
|
||||
strong_ $ toHtml repoName
|
||||
|
||||
div_ [id_ "repo-tab-data"] do
|
||||
|
||||
case tab of
|
||||
|
||||
TreeTab{} -> do
|
||||
|
||||
let tree = [ fromStringMay @GitHash (Text.unpack v)
|
||||
| ("tree", v) <- params
|
||||
] & catMaybes & headMay
|
||||
|
||||
maybe (repoRefs lww) (\t -> repoTree lww t t) tree
|
||||
|
||||
ManifestTab -> do
|
||||
for_ repoHead $ thisRepoManifest
|
||||
|
||||
CommitsTab{} -> do
|
||||
let predicate = Right (fromQueryParams params)
|
||||
repoCommits lww predicate
|
||||
|
||||
ForksTab -> do
|
||||
repoForks lww
|
||||
|
||||
PinnedTab w -> do
|
||||
|
||||
pinned' <- S.toList_ $ for_ pinned $ \(_,ref) -> case ref of
|
||||
PinnedRefBlob s n hash -> do
|
||||
S.yield (hash, (s,n))
|
||||
|
||||
let pinned = Map.fromList pinned'
|
||||
|
||||
void $ runMaybeT do
|
||||
ref <- [ fromStringMay @GitHash (Text.unpack v)
|
||||
| ("ref", v) <- params
|
||||
] & catMaybes
|
||||
& headMay
|
||||
& toMPlus
|
||||
|
||||
(s,n) <- Map.lookup ref pinned & toMPlus
|
||||
|
||||
lift $ repoSomeBlob lww s ref
|
||||
|
||||
mempty
|
||||
|
||||
div_ [id_ "repo-tab-data-embedded"] mempty
|
||||
|
||||
|
||||
thisRepoManifest :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoHead -> HtmlT m ()
|
||||
thisRepoManifest rh = do
|
||||
(_, man) <- lift $ parseManifest rh
|
||||
div_ [class_ "lim-text"] $ toHtmlRaw (renderMarkdown' man)
|
||||
|
||||
repoRefs :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> HtmlT m ()
|
||||
repoRefs lww = asksBaseUrl $ withBaseUrl do
|
||||
|
||||
refs <- lift $ gitShowRefs lww
|
||||
table_ [] do
|
||||
for_ refs $ \(r,h) -> do
|
||||
let r_ = Text.pack $ show $ pretty r
|
||||
let co = show $ pretty h
|
||||
let uri = toBaseURL (RepoTree lww h h)
|
||||
|
||||
let showRef = Text.isPrefixOf "refs" r_
|
||||
|
||||
when showRef do
|
||||
tr_ do
|
||||
td_ do
|
||||
|
||||
if | Text.isPrefixOf "refs/heads" r_ -> do
|
||||
svgIcon IconGitBranch
|
||||
| Text.isPrefixOf "refs/tags" r_ -> do
|
||||
svgIcon IconTag
|
||||
| otherwise -> mempty
|
||||
|
||||
td_ (toHtml r_)
|
||||
td_ [class_ "mono"] $ do
|
||||
a_ [ href_ "#"
|
||||
, hxGet_ uri
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] (toHtml $ show $ pretty h)
|
||||
|
||||
|
||||
treeLocator :: (WithBaseUrl, DashBoardPerks m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> GitHash
|
||||
-> TreeLocator
|
||||
-> HtmlT m ()
|
||||
-> HtmlT m ()
|
||||
|
||||
treeLocator lww co locator next = do
|
||||
|
||||
let repo = show $ pretty $ lww
|
||||
|
||||
let co_ = show $ pretty co
|
||||
|
||||
let prefixSlash x = if fromIntegral x > 1 then span_ "/" else ""
|
||||
let showRoot =
|
||||
[ hxGet_ (toBaseURL (RepoTree lww co co))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
, href_ "#"
|
||||
]
|
||||
|
||||
span_ [] $ a_ [ hxGet_ (toBaseURL (RepoRefs lww))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
, href_ "#"
|
||||
] $ toHtml (take 10 repo <> "..")
|
||||
span_ [] "/"
|
||||
span_ [] $ a_ showRoot $ toHtml (take 10 co_ <> "..")
|
||||
unless (List.null locator) do
|
||||
span_ [] "/"
|
||||
for_ locator $ \(_,this,level,name) -> do
|
||||
prefixSlash level
|
||||
let uri = toBaseURL (RepoTree lww co (coerce @_ @GitHash this))
|
||||
span_ [] do
|
||||
a_ [ href_ "#"
|
||||
, hxGet_ uri
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] (toHtml (show $ pretty name))
|
||||
next
|
||||
|
||||
|
||||
repoTreeEmbedded :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> GitHash -- ^ this
|
||||
-> GitHash -- ^ this
|
||||
-> HtmlT m ()
|
||||
|
||||
repoTreeEmbedded = repoTree_ True
|
||||
|
||||
|
||||
repoTree :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> GitHash -- ^ this
|
||||
-> GitHash -- ^ this
|
||||
-> HtmlT m ()
|
||||
|
||||
repoTree = repoTree_ False
|
||||
|
||||
repoTree_ :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> Bool
|
||||
-> LWWRefKey 'HBS2Basic
|
||||
-> GitHash -- ^ this
|
||||
-> GitHash -- ^ this
|
||||
-> HtmlT m ()
|
||||
|
||||
repoTree_ embed lww co root = asksBaseUrl $ withBaseUrl $ do
|
||||
|
||||
tree <- lift $ gitShowTree lww root
|
||||
back' <- lift $ selectParentTree (TreeCommit co) (TreeTree root)
|
||||
|
||||
let syntaxMap = Sky.defaultSyntaxMap
|
||||
|
||||
let sorted = sortOn (\(tp, _, name) -> (tpOrder tp, name)) tree
|
||||
where
|
||||
tpOrder Tree = (0 :: Int)
|
||||
tpOrder Blob = 1
|
||||
tpOrder _ = 2
|
||||
|
||||
locator <- lift $ selectTreeLocator (TreeCommit co) (TreeTree root)
|
||||
|
||||
let target = if embed then "#repo-tab-data-embedded" else "#repo-tab-data"
|
||||
|
||||
table_ [] do
|
||||
|
||||
unless embed do
|
||||
|
||||
tr_ do
|
||||
td_ [class_ "tree-locator", colspan_ "3"] do
|
||||
treeLocator lww co locator none
|
||||
|
||||
tr_ mempty do
|
||||
|
||||
for_ back' $ \r -> do
|
||||
let rootLink = toBaseURL (RepoTree lww co (coerce @_ @GitHash r))
|
||||
td_ $ svgIcon IconArrowUturnLeft
|
||||
td_ ".."
|
||||
td_ do a_ [ href_ "#"
|
||||
, hxGet_ rootLink
|
||||
, hxTarget_ target
|
||||
] (toHtml $ show $ pretty r)
|
||||
|
||||
for_ sorted $ \(tp,h,name) -> do
|
||||
let itemClass = pretty tp & show & Text.pack
|
||||
let hash_ = show $ pretty h
|
||||
let uri = toBaseURL $ RepoTree lww co h
|
||||
tr_ mempty do
|
||||
td_ $ case tp of
|
||||
Commit -> mempty
|
||||
Tree -> svgIcon IconFolderFilled
|
||||
Blob -> do
|
||||
let syn = Sky.syntaxesByFilename syntaxMap (Text.unpack name)
|
||||
& headMay
|
||||
<&> Text.toLower . Sky.sName
|
||||
|
||||
let icon = case syn of
|
||||
Just "haskell" -> IconHaskell
|
||||
Just "markdown" -> IconMarkdown
|
||||
Just "nix" -> IconNix
|
||||
Just "bash" -> IconBash
|
||||
Just "python" -> IconPython
|
||||
Just "javascript" -> IconJavaScript
|
||||
Just "sql" -> IconSql
|
||||
Just s | s `elem` ["cabal","makefile","toml","ini","yaml"]
|
||||
-> IconSettingsFilled
|
||||
_ -> IconFileFilled
|
||||
|
||||
svgIcon icon
|
||||
|
||||
-- debug $ red "PUSH URL" <+> pretty (path ["back", wtf])
|
||||
|
||||
td_ [class_ itemClass] (toHtml $ show $ pretty name)
|
||||
td_ [class_ "mono"] do
|
||||
case tp of
|
||||
Blob -> do
|
||||
let blobUri = toBaseURL $ RepoBlob lww co root h
|
||||
a_ [ href_ "#"
|
||||
, hxGet_ blobUri
|
||||
, hxTarget_ target
|
||||
] (toHtml hash_)
|
||||
|
||||
Tree -> do
|
||||
a_ [ href_ "#"
|
||||
, hxGet_ uri
|
||||
, hxTarget_ target
|
||||
] (toHtml hash_)
|
||||
|
||||
_ -> mempty
|
||||
|
||||
|
||||
{- HLINT ignore "Functor law" -}
|
||||
|
||||
data RepoCommitStyle = RepoCommitSummary | RepoCommitPatch
|
||||
deriving (Eq,Ord,Show)
|
||||
|
||||
repoCommit :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> RepoCommitStyle
|
||||
-> LWWRefKey 'HBS2Basic
|
||||
-> GitHash
|
||||
-> HtmlT m ()
|
||||
|
||||
repoCommit style lww hash = asksBaseUrl $ withBaseUrl do
|
||||
let syntaxMap = Sky.defaultSyntaxMap
|
||||
|
||||
txt <- lift $ getCommitRawBrief lww hash
|
||||
|
||||
let header = Text.lines txt & takeWhile (not . Text.null)
|
||||
& fmap Text.words
|
||||
|
||||
let au = [ Text.takeWhile (/= '<') (Text.unwords a)
|
||||
| ("Author:" : a) <- header
|
||||
] & headMay
|
||||
|
||||
table_ [class_ "item-attr"] do
|
||||
|
||||
tr_ do
|
||||
th_ [width_ "16rem"] $ strong_ "back"
|
||||
td_ $ a_ [ href_ (toBaseURL (RepoPage (CommitsTab (Just hash)) lww))
|
||||
] $ toHtml $ show $ pretty hash
|
||||
|
||||
for_ au $ \author -> do
|
||||
tr_ do
|
||||
th_ $ strong_ "author"
|
||||
td_ $ toHtml author
|
||||
|
||||
tr_ $ do
|
||||
th_ $ strong_ "view"
|
||||
td_ do
|
||||
ul_ [class_ "misc-menu"]do
|
||||
li_ $ a_ [ href_ "#"
|
||||
, hxGet_ (toBaseURL (RepoCommitSummaryQ lww hash))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] "summary"
|
||||
|
||||
li_ $ a_ [ href_ "#"
|
||||
, hxGet_ (toBaseURL (RepoCommitPatchQ lww hash))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
] "patch"
|
||||
|
||||
li_ $ a_ [ href_ (toBaseURL (RepoPage (TreeTab (Just hash)) lww))
|
||||
] "tree"
|
||||
|
||||
case style of
|
||||
RepoCommitSummary -> do
|
||||
|
||||
let msyn = Sky.syntaxByName syntaxMap "default"
|
||||
|
||||
for_ msyn $ \syn -> do
|
||||
|
||||
let config = TokenizerConfig { traceOutput = False, syntaxMap = syntaxMap }
|
||||
|
||||
case tokenize config syn txt of
|
||||
Left _ -> mempty
|
||||
Right tokens -> do
|
||||
let fo = Sky.defaultFormatOpts { Sky.numberLines = False, Sky.ansiColorLevel = Sky.ANSI256Color }
|
||||
let code = renderText (Lucid.formatHtmlBlock fo tokens)
|
||||
toHtmlRaw code
|
||||
|
||||
RepoCommitPatch -> do
|
||||
|
||||
let msyn = Sky.syntaxByName syntaxMap "diff"
|
||||
|
||||
for_ msyn $ \syn -> do
|
||||
|
||||
txt <- lift $ getCommitRawPatch lww hash
|
||||
|
||||
let config = TokenizerConfig { traceOutput = False, syntaxMap = syntaxMap }
|
||||
|
||||
case tokenize config syn txt of
|
||||
Left _ -> mempty
|
||||
Right tokens -> do
|
||||
let fo = Sky.defaultFormatOpts { Sky.numberLines = False, Sky.ansiColorLevel = Sky.ANSI256Color }
|
||||
let code = renderText (Lucid.formatHtmlBlock fo tokens)
|
||||
toHtmlRaw code
|
||||
|
||||
|
||||
repoForks :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> HtmlT m ()
|
||||
|
||||
repoForks lww = asksBaseUrl $ withBaseUrl do
|
||||
forks <- lift $ selectRepoForks lww
|
||||
now <- getEpoch
|
||||
|
||||
unless (List.null forks) do
|
||||
table_ $ do
|
||||
tr_ $ th_ [colspan_ "3"] mempty
|
||||
for_ forks $ \it@RepoListItem{..} -> do
|
||||
let lwwTo = coerce @_ @(LWWRefKey 'HBS2Basic) rlRepoLww
|
||||
tr_ [class_ "commit-brief-title"] do
|
||||
td_ $ svgIcon IconGitFork
|
||||
td_ [class_ "mono"] $
|
||||
a_ [ href_ (toBaseURL (RepoPage (CommitsTab Nothing) lwwTo))
|
||||
] do
|
||||
toHtmlRaw $ view rlRepoLwwAsText it
|
||||
td_ $ small_ $ toHtml (agePure rlRepoSeq now)
|
||||
|
||||
|
||||
repoCommits :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> Either SelectCommitsPred SelectCommitsPred
|
||||
-> HtmlT m ()
|
||||
|
||||
repoCommits lww predicate' = asksBaseUrl $ withBaseUrl do
|
||||
now <- getEpoch
|
||||
|
||||
debug $ red "repoCommits"
|
||||
|
||||
let predicate = either id id predicate'
|
||||
|
||||
co <- lift $ selectCommits lww predicate
|
||||
|
||||
let off = view commitPredOffset predicate
|
||||
let lim = view commitPredLimit predicate
|
||||
let noff = off + lim
|
||||
|
||||
let query = RepoCommitsQ lww noff lim --) path ["repo", repo, "commits", show noff, show lim]
|
||||
|
||||
let normalizeText s = l $ (Text.take 60 . Text.unwords . Text.words) s
|
||||
where l x | Text.length x < 60 = x
|
||||
| otherwise = x <> "..."
|
||||
|
||||
let rows = do
|
||||
tr_ $ th_ [colspan_ "5"] mempty
|
||||
for_ co $ \case
|
||||
CommitListItemBrief{..} -> do
|
||||
tr_ [class_ "commit-brief-title"] do
|
||||
td_ [class_ "commit-icon"] $ svgIcon IconGitCommit
|
||||
|
||||
td_ [class_ "commit-hash mono"] do
|
||||
let hash = coerce @_ @GitHash commitListHash
|
||||
a_ [ href_ "#"
|
||||
, hxGet_ (toBaseURL (RepoCommitDefault lww hash))
|
||||
, hxTarget_ "#repo-tab-data"
|
||||
, hxPushUrl_ (toBaseURL query)
|
||||
] $ toHtml (ShortRef hash)
|
||||
|
||||
td_ [class_ "commit-brief-title"] do
|
||||
toHtml $ normalizeText $ coerce @_ @Text commitListTitle
|
||||
|
||||
tr_ [class_ "commit-brief-details"] do
|
||||
td_ [colspan_ "3"] do
|
||||
small_ do
|
||||
toHtml (agePure (coerce @_ @Integer commitListTime) now)
|
||||
toHtml " by "
|
||||
toHtml $ coerce @_ @Text commitListAuthor
|
||||
|
||||
unless (List.null co) do
|
||||
tr_ [ class_ "commit-brief-last"
|
||||
, hxGet_ (toBaseURL query)
|
||||
, hxTrigger_ "revealed"
|
||||
, hxSwap_ "afterend"
|
||||
] do
|
||||
td_ [colspan_ "4"] do
|
||||
mempty
|
||||
|
||||
if isRight predicate' then do
|
||||
table_ rows
|
||||
else do
|
||||
rows
|
||||
|
||||
|
||||
repoSomeBlob :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> Text
|
||||
-> GitHash
|
||||
-> HtmlT m ()
|
||||
|
||||
repoSomeBlob lww syn hash = do
|
||||
|
||||
bi <- lift (selectBlobInfo (BlobHash hash))
|
||||
>>= orThrow (itemNotFound hash)
|
||||
|
||||
doRenderBlob (pure mempty) lww bi
|
||||
|
||||
repoBlob :: (DashBoardPerks m, MonadReader DashBoardEnv m)
|
||||
=> LWWRefKey 'HBS2Basic
|
||||
-> TreeCommit
|
||||
-> TreeTree
|
||||
-> BlobInfo
|
||||
-> HtmlT m ()
|
||||
|
||||
repoBlob lww co tree bi@BlobInfo{..} = asksBaseUrl $ withBaseUrl do
|
||||
locator <- lift $ selectTreeLocator co tree
|
||||
|
||||
table_ [] do
|
||||
tr_ do
|
||||
td_ [class_ "tree-locator", colspan_ "3"] do
|
||||
treeLocator lww (coerce co) locator do
|
||||
span_ "/"
|
||||
span_ $ toHtml (show $ pretty blobName)
|
||||
|
||||
|
||||
table_ [class_ "item-attr"] do
|
||||
tr_ do
|
||||
th_ $ strong_ "hash"
|
||||
td_ [colspan_ "7"] do
|
||||
span_ [class_ "mono"] $ toHtml $ show $ pretty blobHash
|
||||
|
||||
tr_ do
|
||||
th_ $ strong_ "syntax"
|
||||
td_ $ toHtml $ show $ pretty blobSyn
|
||||
|
||||
th_ $ strong_ "size"
|
||||
td_ $ toHtml $ show $ pretty blobSize
|
||||
|
||||
td_ [colspan_ "3"] mempty
|
||||
|
||||
doRenderBlob (pure mempty) lww bi
|
|
@ -1,160 +0,0 @@
|
|||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# Language PatternSynonyms #-}
|
||||
{-# Language ViewPatterns #-}
|
||||
{-# Language MultiWayIf #-}
|
||||
module HBS2.Git.Web.Html.Root
|
||||
( module HBS2.Git.Web.Html.Root
|
||||
, module HBS2.Git.Web.Html.Types
|
||||
, module HBS2.Git.Web.Html.Parts.TopInfoBlock
|
||||
) where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.Types
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.Web.Assets
|
||||
|
||||
import HBS2.Git.Web.Html.Types
|
||||
import HBS2.Git.Web.Html.Markdown
|
||||
import HBS2.Git.Web.Html.Parts.TopInfoBlock
|
||||
|
||||
import Lucid.Base
|
||||
import Lucid.Html5 hiding (for_)
|
||||
|
||||
import Data.Word
|
||||
|
||||
|
||||
myCss :: (WithBaseUrl, Monad m) => HtmlT m ()
|
||||
myCss = do
|
||||
link_ [rel_ "stylesheet", href_ (toBaseURL "css/custom.css")]
|
||||
|
||||
hyper_ :: Text -> Attribute
|
||||
hyper_ = makeAttribute "_"
|
||||
|
||||
ariaLabel_ :: Text -> Attribute
|
||||
ariaLabel_ = makeAttribute "aria-label"
|
||||
|
||||
onClickCopy :: Text -> Attribute
|
||||
onClickCopy s =
|
||||
hyper_ [qc|on click writeText('{s}') into the navigator's clipboard
|
||||
set my innerHTML to '{svgIconText IconCopyDone}'
|
||||
set @data-tooltip to 'Copied!'
|
||||
wait 2s
|
||||
set my innerHTML to '{svgIconText IconCopy}'
|
||||
set @data-tooltip to 'Copy'
|
||||
|]
|
||||
|
||||
|
||||
onClickCopyText :: Text -> Attribute
|
||||
onClickCopyText s =
|
||||
hyper_ [qc|on click writeText('{s}') into the navigator's clipboard
|
||||
set @data-tooltip to 'Copied!'
|
||||
wait 2s
|
||||
set @data-tooltip to 'Copy'
|
||||
|]
|
||||
|
||||
|
||||
instance ToHtml RepoBrief where
|
||||
toHtml (RepoBrief txt) = toHtmlRaw (renderMarkdown' txt)
|
||||
toHtmlRaw (RepoBrief txt) = toHtmlRaw (renderMarkdown' txt)
|
||||
|
||||
data WithTime a = WithTime Integer a
|
||||
|
||||
|
||||
instance ToHtml GitRef where
|
||||
toHtml (GitRef s)= toHtml s
|
||||
toHtmlRaw (GitRef s)= toHtmlRaw s
|
||||
|
||||
rootPage :: (WithBaseUrl, Monad m) => HtmlT m () -> HtmlT m ()
|
||||
rootPage content = do
|
||||
doctypehtml_ do
|
||||
head_ do
|
||||
meta_ [charset_ "UTF-8"]
|
||||
meta_ [name_ "viewport", content_ "width=device-width, initial-scale=1.0"]
|
||||
-- FIXME: static-local-loading
|
||||
link_ [rel_ "stylesheet", href_ "https://cdn.jsdelivr.net/npm/@picocss/pico@2.0.6/css/pico.min.css"]
|
||||
script_ [src_ "https://unpkg.com/hyperscript.org@0.9.12"] ""
|
||||
script_ [src_ "https://unpkg.com/htmx.org@1.9.11"] ""
|
||||
myCss
|
||||
|
||||
body_ do
|
||||
|
||||
header_ [class_ "container-fluid"] do
|
||||
nav_ do
|
||||
ul_ $ li_ $ a_ [href_ (toBaseURL RepoListPage)] $ strong_ "hbs2-git dashboard"
|
||||
|
||||
content
|
||||
|
||||
|
||||
dashboardRootPage :: (DashBoardPerks m, MonadReader DashBoardEnv m) => HtmlT m ()
|
||||
dashboardRootPage = asksBaseUrl $ withBaseUrl $ rootPage do
|
||||
|
||||
items <- lift $ selectRepoList mempty
|
||||
|
||||
now <- liftIO getPOSIXTime <&> fromIntegral . round
|
||||
|
||||
main_ [class_ "container-fluid"] $ do
|
||||
div_ [class_ "wrapper"] $ do
|
||||
aside_ [class_ "sidebar"] $ do
|
||||
div_ [class_ "info-block"] $ small_ "Всякая разная рандомная информация хрен знает, что тут пока выводить"
|
||||
div_ [class_ "info-block"] $ small_ "Всякая разная рандомная информация хрен знает, что тут пока выводить"
|
||||
|
||||
div_ [class_ "content"] do
|
||||
|
||||
section_ do
|
||||
h2_ "Git repositories"
|
||||
form_ [role_ "search"] do
|
||||
input_ [name_ "search", type_ "search"]
|
||||
input_ [type_ "submit", value_ "Search"]
|
||||
|
||||
section_ do
|
||||
|
||||
for_ items $ \it@RepoListItem{..} -> do
|
||||
|
||||
let locked = isJust $ coerce @_ @(Maybe HashRef) rlRepoGK0
|
||||
|
||||
let url = toBaseURL (RepoPage (CommitsTab Nothing) (coerce @_ @(LWWRefKey 'HBS2Basic) rlRepoLww))
|
||||
-- path ["repo", Text.unpack $ view rlRepoLwwAsText it]
|
||||
let t = fromIntegral $ coerce @_ @Word64 rlRepoSeq
|
||||
|
||||
let updated = agePure t now
|
||||
|
||||
article_ [class_ "repo-list-item"] do
|
||||
div_ do
|
||||
|
||||
h5_ do
|
||||
toHtml rlRepoName
|
||||
|
||||
div_ [class_ "repo-list-item-link-wrapper"] $ do
|
||||
a_ [href_ url] (toHtml $ view rlRepoLwwAsText it)
|
||||
button_ [class_ "copy-button", onClickCopy (view rlRepoLwwAsText it), data_ "tooltip" "Copy"] do
|
||||
svgIcon IconCopy
|
||||
|
||||
toHtml rlRepoBrief
|
||||
|
||||
div_ do
|
||||
|
||||
div_ [class_ "whitespace-nowrap"] do
|
||||
small_ $ "Updated " <> toHtml updated
|
||||
|
||||
when locked do
|
||||
div_ do
|
||||
small_ do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconLockClosed
|
||||
"Encrypted"
|
||||
|
||||
div_ do
|
||||
small_ do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconGitCommit
|
||||
strong_ $ toHtml $ show rlRepoCommits
|
||||
" commits"
|
||||
|
||||
div_ do
|
||||
small_ do
|
||||
span_ [class_ "inline-icon-wrapper"] $ svgIcon IconGitFork
|
||||
strong_ $ toHtml $ show rlRepoForks
|
||||
" forks"
|
||||
|
||||
|
||||
tabClick :: Attribute
|
||||
tabClick =
|
||||
hyper_ "on click take .contrast from .tab for event's target"
|
|
@ -1,322 +0,0 @@
|
|||
{-# Language MultiWayIf #-}
|
||||
{-# Language ImplicitParams #-}
|
||||
module HBS2.Git.Web.Html.Types where
|
||||
|
||||
import HBS2.Git.DashBoard.Prelude
|
||||
import HBS2.Git.DashBoard.State
|
||||
import HBS2.Git.DashBoard.Fixme as Fixme
|
||||
|
||||
import Data.Kind
|
||||
import Data.Map (Map)
|
||||
import Data.Map qualified as Map
|
||||
import Data.Text qualified as Text
|
||||
import Data.Word
|
||||
import Lucid.Base
|
||||
import Network.URI.Encode
|
||||
import System.FilePath
|
||||
import Web.Scotty.Trans as Scotty
|
||||
|
||||
import Network.HTTP.Types.Status
|
||||
|
||||
newtype H a = H a
|
||||
|
||||
raiseStatus :: forall m . MonadIO m => Status -> Text -> m ()
|
||||
raiseStatus s t = throwIO (StatusError s t)
|
||||
|
||||
itemNotFound s = StatusError status404 (Text.pack $ show $ pretty s)
|
||||
|
||||
rootPath :: [String] -> [String]
|
||||
rootPath = ("/":)
|
||||
|
||||
data Domain = FixmeDomain
|
||||
|
||||
newtype FromParams (e :: Domain) a = FromParams a
|
||||
|
||||
class Path a where
|
||||
path :: [a] -> Text
|
||||
|
||||
instance Path String where
|
||||
path = Text.pack . joinPath . rootPath
|
||||
|
||||
|
||||
class ToRoutePattern a where
|
||||
routePattern :: a -> RoutePattern
|
||||
|
||||
type WithBaseUrl = ?dashBoardBaseUrl :: Text
|
||||
|
||||
getBaseUrl :: WithBaseUrl => Text
|
||||
getBaseUrl = ?dashBoardBaseUrl
|
||||
|
||||
withBaseUrl :: (WithBaseUrl => r) -> Text -> r
|
||||
withBaseUrl thingInside baseUrl =
|
||||
let ?dashBoardBaseUrl = baseUrl in thingInside
|
||||
|
||||
toBaseURL :: (WithBaseUrl, ToURL a) => a -> Text
|
||||
toBaseURL x = getBaseUrl <> toURL x
|
||||
|
||||
class ToURL a where
|
||||
toURL :: a -> Text
|
||||
|
||||
data family Tabs a :: Type
|
||||
|
||||
data RepoListPage = RepoListPage
|
||||
|
||||
data RepoPageTabs = CommitsTab (Maybe GitHash)
|
||||
| ManifestTab
|
||||
| TreeTab (Maybe GitHash)
|
||||
| IssuesTab
|
||||
| ForksTab
|
||||
| PinnedTab (Maybe (Text, Text, GitHash))
|
||||
deriving stock (Eq,Ord,Show)
|
||||
|
||||
data RepoPage s a = RepoPage s a
|
||||
|
||||
data RepoRefs repo = RepoRefs repo
|
||||
|
||||
data RepoTree repo commit tree = RepoTree repo commit tree
|
||||
|
||||
data RepoTreeEmbedded repo commit tree = RepoTreeEmbedded repo commit tree
|
||||
|
||||
data RepoBlob repo commit tree blob = RepoBlob repo commit tree blob
|
||||
|
||||
data RepoSomeBlob repo blob tp = RepoSomeBlob repo blob tp
|
||||
|
||||
data RepoForksHtmx repo = RepoForksHtmx repo
|
||||
|
||||
newtype RepoManifest repo = RepoManifest repo
|
||||
|
||||
newtype RepoCommits repo = RepoCommits repo
|
||||
|
||||
data Paged q = Paged QueryOffset q
|
||||
|
||||
data RepoFixmeHtmx repo = RepoFixmeHtmx (Map Text Text) repo
|
||||
|
||||
data RepoCommitsQ repo off lim = RepoCommitsQ repo off lim
|
||||
|
||||
data RepoCommitDefault repo commit = RepoCommitDefault repo commit
|
||||
|
||||
data RepoCommitSummaryQ repo commit = RepoCommitSummaryQ repo commit
|
||||
|
||||
data RepoCommitPatchQ repo commit = RepoCommitPatchQ repo commit
|
||||
|
||||
data IssuePage repo issue = IssuePage repo issue
|
||||
|
||||
|
||||
newtype ShortRef a = ShortRef a
|
||||
|
||||
shortRef :: Int -> Int -> String -> String
|
||||
shortRef n k a = if k > 0 then [qc|{b}..{r}|] else [qc|{b}|]
|
||||
where
|
||||
b = take n a
|
||||
r = reverse $ take k (reverse a)
|
||||
|
||||
instance ToHtml (ShortRef GitHash) where
|
||||
toHtml (ShortRef a) = toHtml (shortRef 10 0 (show $ pretty a))
|
||||
toHtmlRaw (ShortRef a) = toHtml (shortRef 10 0 (show $ pretty a))
|
||||
|
||||
instance ToHtml (ShortRef (LWWRefKey 'HBS2Basic)) where
|
||||
toHtml (ShortRef a) = toHtml (shortRef 14 3 (show $ pretty a))
|
||||
toHtmlRaw (ShortRef a) = toHtml (shortRef 14 3 (show $ pretty a))
|
||||
|
||||
|
||||
toArg :: (Semigroup a, IsString a) => a -> a
|
||||
toArg s = ":" <> s
|
||||
|
||||
toPattern :: Text -> RoutePattern
|
||||
toPattern = fromString . Text.unpack
|
||||
|
||||
|
||||
instance Pretty RepoPageTabs where
|
||||
pretty = \case
|
||||
CommitsTab{} -> "commits"
|
||||
ManifestTab{} -> "manifest"
|
||||
TreeTab{} -> "tree"
|
||||
ForksTab{} -> "forks"
|
||||
IssuesTab{} -> "issues"
|
||||
PinnedTab{} -> "pinned"
|
||||
|
||||
instance FromStringMaybe RepoPageTabs where
|
||||
fromStringMay = \case
|
||||
"commits" -> pure (CommitsTab Nothing)
|
||||
"manifest" -> pure ManifestTab
|
||||
"tree" -> pure (TreeTab Nothing)
|
||||
"forks" -> pure ForksTab
|
||||
"issues" -> pure IssuesTab
|
||||
"pinned" -> pure $ PinnedTab Nothing
|
||||
_ -> pure (CommitsTab Nothing)
|
||||
|
||||
|
||||
instance ToRoutePattern RepoListPage where
|
||||
routePattern = \case
|
||||
RepoListPage -> "/"
|
||||
|
||||
instance ToURL String where
|
||||
toURL str = path [str]
|
||||
|
||||
instance ToURL (RepoPage RepoPageTabs (LWWRefKey 'HBS2Basic)) where
|
||||
toURL (RepoPage s w) = path @String [ "/", show (pretty s), show (pretty w)]
|
||||
<> pred_
|
||||
where
|
||||
-- FIXME: use-uri-encode
|
||||
pred_ = case s of
|
||||
CommitsTab (Just p) -> Text.pack $ "?ref=" <> show (pretty p)
|
||||
TreeTab (Just p) -> Text.pack $ "?tree=" <> show (pretty p)
|
||||
PinnedTab (Just (s,n,h)) -> Text.pack $ "?ref=" <> show (pretty h)
|
||||
_ -> mempty
|
||||
|
||||
instance ToRoutePattern (RepoPage String String) where
|
||||
routePattern (RepoPage s w) = path ["/", toArg s, toArg w] & toPattern
|
||||
|
||||
instance ToURL RepoListPage where
|
||||
toURL _ = "/"
|
||||
|
||||
instance ToURL (RepoRefs (LWWRefKey 'HBS2Basic)) where
|
||||
toURL (RepoRefs repo') = path ["/", "htmx", "refs", repo]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
|
||||
instance ToRoutePattern (RepoRefs String) where
|
||||
routePattern (RepoRefs s) = path ["/", "htmx", "refs", toArg s] & toPattern
|
||||
|
||||
|
||||
instance ToURL (RepoTree (LWWRefKey 'HBS2Basic) GitHash GitHash) where
|
||||
toURL (RepoTree k co tree') = path ["/", "htmx", "tree", repo, commit, tree]
|
||||
where
|
||||
repo = show $ pretty k
|
||||
commit = show $ pretty co
|
||||
tree = show $ pretty tree'
|
||||
|
||||
instance ToRoutePattern (RepoTree String String String) where
|
||||
routePattern (RepoTree r co tree) =
|
||||
path ["/", "htmx", "tree", toArg r, toArg co, toArg tree] & toPattern
|
||||
|
||||
instance ToURL (RepoBlob (LWWRefKey 'HBS2Basic) GitHash GitHash GitHash) where
|
||||
toURL (RepoBlob k co t bo) = path ["/", "htmx", "blob", repo, commit, tree, blob]
|
||||
where
|
||||
repo = show $ pretty k
|
||||
commit = show $ pretty co
|
||||
tree = show $ pretty t
|
||||
blob = show $ pretty bo
|
||||
|
||||
instance ToRoutePattern (RepoBlob String String String String) where
|
||||
routePattern (RepoBlob r c t b) =
|
||||
path ["/", "htmx", "blob", toArg r, toArg c, toArg t, toArg b] & toPattern
|
||||
|
||||
|
||||
instance ToURL (RepoSomeBlob (LWWRefKey 'HBS2Basic) Text GitHash) where
|
||||
toURL (RepoSomeBlob k tp' blo) = path ["/", "htmx", "some-blob", repo, tp, blob]
|
||||
where
|
||||
repo = show $ pretty k
|
||||
tp = Text.unpack tp'
|
||||
blob = show $ pretty blo
|
||||
|
||||
instance ToRoutePattern (RepoSomeBlob String String String) where
|
||||
routePattern (RepoSomeBlob r t b) =
|
||||
path ["/", "htmx", "some-blob", toArg r, toArg t, toArg b] & toPattern
|
||||
|
||||
instance ToURL (RepoManifest (LWWRefKey 'HBS2Basic)) where
|
||||
toURL (RepoManifest repo') = path ["/", "htmx", "manifest", repo]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
|
||||
instance ToRoutePattern (RepoManifest String) where
|
||||
routePattern (RepoManifest s) = path ["/", "htmx", "manifest", toArg s] & toPattern
|
||||
|
||||
instance ToURL (RepoCommits (LWWRefKey 'HBS2Basic)) where
|
||||
toURL (RepoCommits repo') = path ["/", "htmx", "commits", repo]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
|
||||
instance ToRoutePattern (RepoCommits String) where
|
||||
routePattern (RepoCommits s) = path ["/", "htmx", "commits", toArg s] & toPattern
|
||||
|
||||
instance ToURL (RepoCommitsQ (LWWRefKey 'HBS2Basic) Int Int) where
|
||||
toURL (RepoCommitsQ repo' off lim) = path ["/", "htmx", "commits", repo, show off, show lim]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
|
||||
instance ToRoutePattern (RepoCommitsQ String String String) where
|
||||
routePattern (RepoCommitsQ r o l) =
|
||||
path ["/", "htmx", "commits", toArg r, toArg o, toArg l] & toPattern
|
||||
|
||||
instance ToURL (RepoCommitDefault (LWWRefKey 'HBS2Basic) GitHash) where
|
||||
toURL (RepoCommitDefault repo' h) = toURL (RepoCommitSummaryQ repo' h)
|
||||
|
||||
instance ToRoutePattern (RepoCommitDefault String String) where
|
||||
routePattern (RepoCommitDefault r h) = routePattern (RepoCommitSummaryQ r h)
|
||||
|
||||
instance ToURL (RepoCommitSummaryQ (LWWRefKey 'HBS2Basic) GitHash) where
|
||||
toURL (RepoCommitSummaryQ repo' h) = path ["/", "htmx", "commit", "summary", repo, ha]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
ha = show $ pretty h
|
||||
|
||||
instance ToRoutePattern (RepoCommitSummaryQ String String) where
|
||||
routePattern (RepoCommitSummaryQ r h) =
|
||||
path ["/", "htmx", "commit", "summary", toArg r, toArg h] & toPattern
|
||||
|
||||
instance ToURL (RepoCommitPatchQ (LWWRefKey 'HBS2Basic) GitHash) where
|
||||
toURL (RepoCommitPatchQ repo' h) = path ["/", "htmx", "commit", "patch", repo, ha]
|
||||
where
|
||||
repo = show $ pretty repo'
|
||||
ha = show $ pretty h
|
||||
|
||||
instance ToRoutePattern (RepoCommitPatchQ String String) where
|
||||
routePattern (RepoCommitPatchQ r h) =
|
||||
path ["/", "htmx", "commit", "patch", toArg r, toArg h] & toPattern
|
||||
|
||||
|
||||
instance ToURL (RepoTreeEmbedded (LWWRefKey 'HBS2Basic) GitHash GitHash) where
|
||||
toURL (RepoTreeEmbedded k co tree') = path ["/", "htmx", "tree", "embedded", repo, commit, tree]
|
||||
where
|
||||
repo = show $ pretty k
|
||||
commit = show $ pretty co
|
||||
tree = show $ pretty tree'
|
||||
|
||||
instance ToRoutePattern (RepoTreeEmbedded String String String) where
|
||||
routePattern (RepoTreeEmbedded r co tree) =
|
||||
path ["/", "htmx", "tree", "embedded", toArg r, toArg co, toArg tree] & toPattern
|
||||
|
||||
|
||||
instance ToURL (RepoForksHtmx (LWWRefKey 'HBS2Basic)) where
|
||||
toURL (RepoForksHtmx k) = path ["/", "htmx", "forks", repo]
|
||||
where
|
||||
repo = show $ pretty k
|
||||
|
||||
instance ToRoutePattern (RepoFixmeHtmx String) where
|
||||
routePattern (RepoFixmeHtmx _ r) =
|
||||
path ["/", "htmx", "fixme", toArg r] & toPattern
|
||||
|
||||
instance ToURL (RepoFixmeHtmx RepoLww) where
|
||||
toURL (RepoFixmeHtmx argz' k) = path ["/", "htmx", "fixme", repo] <> "?" <> filtPart
|
||||
where
|
||||
repo = show $ pretty k
|
||||
filtPart = Text.intercalate "&" [ [qc|{encodeText k}={encodeText v}|] | (k,v) <- argz ]
|
||||
argz = Map.toList argz'
|
||||
|
||||
instance ToURL (Paged (RepoFixmeHtmx RepoLww)) where
|
||||
toURL (Paged p (RepoFixmeHtmx a k)) = toURL (RepoFixmeHtmx paged k)
|
||||
where paged = Map.insert "$page" (Text.pack (show p)) a
|
||||
|
||||
instance ToRoutePattern (RepoForksHtmx String) where
|
||||
routePattern (RepoForksHtmx r) =
|
||||
path ["/", "htmx", "forks", toArg r] & toPattern
|
||||
|
||||
|
||||
instance ToRoutePattern (IssuePage String String) where
|
||||
routePattern (IssuePage s w) = path ["/", "issues", toArg s, toArg w] & toPattern
|
||||
|
||||
instance ToURL (IssuePage RepoLww FixmeKey) where
|
||||
toURL (IssuePage r i) = path ["/", "issues", repo, issue]
|
||||
where
|
||||
repo = show $ pretty r
|
||||
issue = show $ pretty i
|
||||
|
||||
|
||||
agePure :: forall a b . (Integral a,Integral b) => a -> b -> Text
|
||||
agePure t0 t = do
|
||||
let sec = fromIntegral @_ @Word64 t - fromIntegral t0
|
||||
fromString $ show $
|
||||
if | sec > 86400 -> pretty (sec `div` 86400) <+> "days ago"
|
||||
| sec > 3600 -> pretty (sec `div` 3600) <+> "hours ago"
|
||||
| otherwise -> pretty (sec `div` 60) <+> "minutes ago"
|
|
@ -1,221 +0,0 @@
|
|||
cabal-version: 3.0
|
||||
name: hbs2-git-dashboard
|
||||
version: 0.25.0.1
|
||||
license: BSD-3-Clause
|
||||
author: Dmitry Zuikov
|
||||
category: System
|
||||
build-type: Simple
|
||||
|
||||
common shared-properties
|
||||
ghc-options:
|
||||
-Wall
|
||||
-fno-warn-type-defaults
|
||||
-fno-warn-unused-matches
|
||||
-fno-warn-name-shadowing
|
||||
-O2
|
||||
|
||||
default-language: GHC2021
|
||||
|
||||
default-extensions:
|
||||
ApplicativeDo
|
||||
, BangPatterns
|
||||
, BlockArguments
|
||||
, ConstraintKinds
|
||||
, DataKinds
|
||||
, DeriveDataTypeable
|
||||
, DeriveGeneric
|
||||
, DerivingStrategies
|
||||
, DerivingVia
|
||||
, ExtendedDefaultRules
|
||||
, FlexibleContexts
|
||||
, FlexibleInstances
|
||||
, GADTs
|
||||
, GeneralizedNewtypeDeriving
|
||||
, ImportQualifiedPost
|
||||
, LambdaCase
|
||||
, MultiParamTypeClasses
|
||||
, OverloadedStrings
|
||||
, QuasiQuotes
|
||||
, RecordWildCards
|
||||
, ScopedTypeVariables
|
||||
, StandaloneDeriving
|
||||
, TupleSections
|
||||
, TypeApplications
|
||||
, TypeFamilies
|
||||
|
||||
|
||||
library hbs2-git-dashboard-assets
|
||||
import: shared-properties
|
||||
|
||||
build-depends:
|
||||
base
|
||||
, bytestring
|
||||
, interpolatedstring-perl6
|
||||
, file-embed
|
||||
, lucid
|
||||
, text
|
||||
|
||||
exposed-modules:
|
||||
HBS2.Git.Web.Assets
|
||||
|
||||
hs-source-dirs: hbs2-git-dashboard-assets
|
||||
|
||||
default-language: GHC2021
|
||||
|
||||
|
||||
library hbs2-git-dashboard-core
|
||||
import: shared-properties
|
||||
|
||||
build-depends:
|
||||
, base
|
||||
|
||||
, hbs2-git-dashboard-assets
|
||||
, hbs2-core
|
||||
, hbs2-peer
|
||||
, hbs2-storage-simple
|
||||
, hbs2-git
|
||||
, hbs2-keyman-direct-lib
|
||||
, db-pipe
|
||||
, suckless-conf
|
||||
, fixme-new
|
||||
|
||||
, aeson
|
||||
, atomic-write
|
||||
, attoparsec
|
||||
, binary
|
||||
, bytestring
|
||||
, containers
|
||||
, deriving-compat
|
||||
, directory
|
||||
, exceptions
|
||||
, filepath
|
||||
, filepattern
|
||||
, generic-data
|
||||
, generic-deriving
|
||||
, generic-lens
|
||||
, http-types
|
||||
, interpolatedstring-perl6
|
||||
, lucid
|
||||
, lucid-htmx
|
||||
, memory
|
||||
, microlens-platform
|
||||
, mtl
|
||||
, network-uri
|
||||
, optparse-applicative
|
||||
, pandoc
|
||||
, prettyprinter
|
||||
, prettyprinter-ansi-terminal
|
||||
, random
|
||||
, safe
|
||||
, scotty >= 0.21
|
||||
, serialise
|
||||
, skylighting
|
||||
, skylighting-core
|
||||
, skylighting-lucid
|
||||
, stm
|
||||
, streaming
|
||||
, split
|
||||
, temporary
|
||||
, text
|
||||
, time
|
||||
, timeit
|
||||
, transformers
|
||||
, typed-process
|
||||
, unix
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
, unordered-containers
|
||||
, uri-encode
|
||||
, vector
|
||||
, wai
|
||||
, wai-extra
|
||||
, wai-middleware-static
|
||||
, wai-middleware-static-embedded
|
||||
, zlib
|
||||
|
||||
exposed-modules:
|
||||
HBS2.Git.DashBoard.Prelude
|
||||
HBS2.Git.DashBoard.Types
|
||||
HBS2.Git.DashBoard.State
|
||||
HBS2.Git.DashBoard.State.Commits
|
||||
HBS2.Git.DashBoard.State.Index
|
||||
HBS2.Git.DashBoard.State.Index.Channels
|
||||
HBS2.Git.DashBoard.State.Index.Peer
|
||||
HBS2.Git.DashBoard.Manifest
|
||||
HBS2.Git.DashBoard.Fixme
|
||||
HBS2.Git.Web.Html.Types
|
||||
HBS2.Git.Web.Html.Parts.TopInfoBlock
|
||||
HBS2.Git.Web.Html.Parts.Issues.Sidebar
|
||||
HBS2.Git.Web.Html.Parts.Blob
|
||||
HBS2.Git.Web.Html.Markdown
|
||||
HBS2.Git.Web.Html.Root
|
||||
HBS2.Git.Web.Html.Issue
|
||||
HBS2.Git.Web.Html.Repo
|
||||
HBS2.Git.Web.Html.Fixme
|
||||
|
||||
hs-source-dirs: hbs2-git-dashboard-core
|
||||
|
||||
default-language: GHC2021
|
||||
|
||||
|
||||
executable hbs2-git-dashboard
|
||||
import: shared-properties
|
||||
main-is: GitDashBoard.hs
|
||||
|
||||
ghc-options:
|
||||
-threaded
|
||||
-rtsopts
|
||||
-O2
|
||||
"-with-rtsopts=-N4 -A64m -AL256m -I0"
|
||||
|
||||
other-modules:
|
||||
|
||||
-- other-extensions:
|
||||
build-depends:
|
||||
base
|
||||
|
||||
, hbs2-core
|
||||
, hbs2-git
|
||||
, hbs2-git-dashboard-assets
|
||||
, hbs2-git-dashboard-core
|
||||
, hbs2-peer
|
||||
, suckless-conf
|
||||
, db-pipe
|
||||
|
||||
, binary
|
||||
, bytestring
|
||||
, deriving-compat
|
||||
, directory
|
||||
, filepath
|
||||
, generic-data
|
||||
, generic-deriving
|
||||
, http-types
|
||||
, lucid
|
||||
, lucid-htmx
|
||||
, mtl
|
||||
, network-uri
|
||||
, optparse-applicative
|
||||
, pandoc
|
||||
, random
|
||||
, scotty >= 0.21
|
||||
, skylighting
|
||||
, skylighting-core
|
||||
, skylighting-lucid
|
||||
, stm
|
||||
, temporary
|
||||
, text
|
||||
, transformers
|
||||
, typed-process
|
||||
, unordered-containers
|
||||
, vector
|
||||
, wai
|
||||
, wai-extra
|
||||
, wai-middleware-static
|
||||
, wai-middleware-static-embedded
|
||||
|
||||
hs-source-dirs:
|
||||
app
|
||||
|
||||
default-language: GHC2021
|
||||
|
||||
|
Loading…
Reference in New Issue