From 96b5b051b3c763c2a07852ccf986901c03d8c560 Mon Sep 17 00:00:00 2001 From: voidlizard Date: Tue, 11 Feb 2025 13:09:46 +0300 Subject: [PATCH] hbs2-dashboard and hbs2-fixer removed --- flake.nix | 3 - hbs2-fixer/LICENSE | 0 hbs2-fixer/app/Main.hs | 724 ----------- hbs2-fixer/examples/fixer-config-example.scm | 72 - hbs2-fixer/examples/getenv.scm | 4 - hbs2-fixer/examples/home.scm | 10 - hbs2-fixer/examples/listen.scm | 25 - hbs2-fixer/examples/runeval.scm | 5 - hbs2-fixer/examples/watch.scm | 4 - hbs2-fixer/hbs2-fixer.cabal | 129 -- hbs2-fixer/real-config-example | 199 --- hbs2-git-dashboard/app/GitDashBoard.hs | 794 ------------ .../HBS2/Git/Web/Assets.hs | 201 --- .../assets/css/custom.css | 370 ------ .../assets/css/pico.min.css | 4 - .../HBS2/Git/DashBoard/Fixme.hs | 142 -- .../HBS2/Git/DashBoard/Manifest.hs | 55 - .../HBS2/Git/DashBoard/Prelude.hs | 65 - .../HBS2/Git/DashBoard/State.hs | 1153 ----------------- .../HBS2/Git/DashBoard/State/Commits.hs | 162 --- .../HBS2/Git/DashBoard/State/Index.hs | 20 - .../Git/DashBoard/State/Index/Channels.hs | 75 -- .../HBS2/Git/DashBoard/State/Index/Peer.hs | 146 --- .../HBS2/Git/DashBoard/Types.hs | 174 --- .../HBS2/Git/Web/Html/Fixme.hs | 102 -- .../HBS2/Git/Web/Html/Issue.hs | 156 --- .../HBS2/Git/Web/Html/Markdown.hs | 24 - .../HBS2/Git/Web/Html/Parts/Blob.hs | 79 -- .../HBS2/Git/Web/Html/Parts/Issues/Sidebar.hs | 105 -- .../HBS2/Git/Web/Html/Parts/TopInfoBlock.hs | 152 --- .../HBS2/Git/Web/Html/Repo.hs | 593 --------- .../HBS2/Git/Web/Html/Root.hs | 160 --- .../HBS2/Git/Web/Html/Types.hs | 322 ----- hbs2-git-dashboard/hbs2-git-dashboard.cabal | 221 ---- 34 files changed, 6450 deletions(-) delete mode 100644 hbs2-fixer/LICENSE delete mode 100644 hbs2-fixer/app/Main.hs delete mode 100644 hbs2-fixer/examples/fixer-config-example.scm delete mode 100644 hbs2-fixer/examples/getenv.scm delete mode 100644 hbs2-fixer/examples/home.scm delete mode 100644 hbs2-fixer/examples/listen.scm delete mode 100644 hbs2-fixer/examples/runeval.scm delete mode 100644 hbs2-fixer/examples/watch.scm delete mode 100644 hbs2-fixer/hbs2-fixer.cabal delete mode 100644 hbs2-fixer/real-config-example delete mode 100644 hbs2-git-dashboard/app/GitDashBoard.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-assets/HBS2/Git/Web/Assets.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/custom.css delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/pico.min.css delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Fixme.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Manifest.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Prelude.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Commits.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Channels.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Peer.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Types.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Fixme.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Issue.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Markdown.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Blob.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Issues/Sidebar.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/TopInfoBlock.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Repo.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Root.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Types.hs delete mode 100644 hbs2-git-dashboard/hbs2-git-dashboard.cabal diff --git a/flake.nix b/flake.nix index 5fb20f24..c24e3018 100644 --- a/flake.nix +++ b/flake.nix @@ -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" diff --git a/hbs2-fixer/LICENSE b/hbs2-fixer/LICENSE deleted file mode 100644 index e69de29b..00000000 diff --git a/hbs2-fixer/app/Main.hs b/hbs2-fixer/app/Main.hs deleted file mode 100644 index a350ee98..00000000 --- a/hbs2-fixer/app/Main.hs +++ /dev/null @@ -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 - diff --git a/hbs2-fixer/examples/fixer-config-example.scm b/hbs2-fixer/examples/fixer-config-example.scm deleted file mode 100644 index 9fc30af4..00000000 --- a/hbs2-fixer/examples/fixer-config-example.scm +++ /dev/null @@ -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") - diff --git a/hbs2-fixer/examples/getenv.scm b/hbs2-fixer/examples/getenv.scm deleted file mode 100644 index bec6a2b2..00000000 --- a/hbs2-fixer/examples/getenv.scm +++ /dev/null @@ -1,4 +0,0 @@ - -(display (getenv 1234)) - -(display (getenv "HOME")) diff --git a/hbs2-fixer/examples/home.scm b/hbs2-fixer/examples/home.scm deleted file mode 100644 index e7df8d4e..00000000 --- a/hbs2-fixer/examples/home.scm +++ /dev/null @@ -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) - diff --git a/hbs2-fixer/examples/listen.scm b/hbs2-fixer/examples/listen.scm deleted file mode 100644 index def7da5a..00000000 --- a/hbs2-fixer/examples/listen.scm +++ /dev/null @@ -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!") diff --git a/hbs2-fixer/examples/runeval.scm b/hbs2-fixer/examples/runeval.scm deleted file mode 100644 index df99c912..00000000 --- a/hbs2-fixer/examples/runeval.scm +++ /dev/null @@ -1,5 +0,0 @@ - -(local code (list (display "HELLO"))) - -(eval code) - diff --git a/hbs2-fixer/examples/watch.scm b/hbs2-fixer/examples/watch.scm deleted file mode 100644 index a5f90d0a..00000000 --- a/hbs2-fixer/examples/watch.scm +++ /dev/null @@ -1,4 +0,0 @@ - -(watch 30 (lwwref "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP") - (display "PREVED") -) diff --git a/hbs2-fixer/hbs2-fixer.cabal b/hbs2-fixer/hbs2-fixer.cabal deleted file mode 100644 index b742e373..00000000 --- a/hbs2-fixer/hbs2-fixer.cabal +++ /dev/null @@ -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 - - - diff --git a/hbs2-fixer/real-config-example b/hbs2-fixer/real-config-example deleted file mode 100644 index 8b7caf98..00000000 --- a/hbs2-fixer/real-config-example +++ /dev/null @@ -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"))) - - ) -) - - diff --git a/hbs2-git-dashboard/app/GitDashBoard.hs b/hbs2-git-dashboard/app/GitDashBoard.hs deleted file mode 100644 index 356d21cc..00000000 --- a/hbs2-git-dashboard/app/GitDashBoard.hs +++ /dev/null @@ -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 diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-assets/HBS2/Git/Web/Assets.hs b/hbs2-git-dashboard/hbs2-git-dashboard-assets/HBS2/Git/Web/Assets.hs deleted file mode 100644 index 6fa86812..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-assets/HBS2/Git/Web/Assets.hs +++ /dev/null @@ -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| - - - -|] - -svgIconText IconCopyDone = [qc| - - - - -|] - -svgIconText IconLockClosed = [qc| - - - - -|] - -svgIconText IconGitCommit = [qc| - - - - -|] - -svgIconText IconGitFork = [qc| - - - - - - -|] - -svgIconText IconGitBranch = [qc| - - - - - - - -|] - -svgIconText IconTag = [qc| - - - -|] - -svgIconText IconFolderFilled = [qc| - - -|] - -svgIconText IconHaskell = [qc| - Haskell - - |] - -svgIconText IconMarkdown = [qc| - Markdown - - |] - -svgIconText IconNix = [qc| - Nix - - |] - -svgIconText IconBash = [qc| - - - - -|] - -svgIconText IconPython = [qc| - Python - -|] - -svgIconText IconJavaScript = [qc| - JavaScript - -|] - -svgIconText IconSql = [qc| - - - - - - - - -|] - -svgIconText IconSettingsFilled = [qc| - - -|] - -svgIconText IconFileFilled = [qc| - - - -|] - -svgIconText IconRefresh = [qc| - - - -|] - -svgIconText IconArrowUturnLeft = [qc| - - - -|] - -svgIconText IconLicense = [qc| - - - - -|] - -svgIconText IconPinned = [qc| - - - - -|] - - -svgIconText IconFixme = [qc| - - - - - - -|] - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/custom.css b/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/custom.css deleted file mode 100644 index f677f2d5..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/custom.css +++ /dev/null @@ -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 */ - - - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/pico.min.css b/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/pico.min.css deleted file mode 100644 index 5928ed78..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-assets/assets/css/pico.min.css +++ /dev/null @@ -1,4 +0,0 @@ -@charset "UTF-8";/*! - * Pico CSS ✨ v2.0.6 (https://picocss.com) - * Copyright 2019-2024 - Licensed under MIT - */:root{--pico-font-family-emoji:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--pico-font-family-sans-serif:system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-grid-column-gap:var(--pico-spacing);--pico-grid-row-gap:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:">";--pico-icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--pico-icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--pico-icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E")}@media (min-width:576px){:root{--pico-font-size:106.25%}}@media (min-width:768px){:root{--pico-font-size:112.5%}}@media (min-width:1024px){:root{--pico-font-size:118.75%}}@media (min-width:1280px){:root{--pico-font-size:125%}}@media (min-width:1536px){:root{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}a.contrast,a.secondary{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}details.dropdown summary:not([role=button]){--pico-outline-width:0.0625rem}nav details.dropdown summary:focus-visible{--pico-outline-width:0.125rem}[role=search]{--pico-border-radius:5rem}[role=group]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus),[role=search]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[role=group]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus),[role=search]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]:not(.outline)::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0) invert(1)}:root:not([data-theme=dark]),[data-theme=light]{--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(2, 154, 232, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:#e7eaf0;--pico-primary:#0172ad;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 114, 173, 0.5);--pico-primary-hover:#015887;--pico-primary-hover-background:#02659a;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(2, 154, 232, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),0 0 0 0.0625rem rgba(129, 145, 181, 0.015);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:#fde7c0;--pico-mark-color:#0f1114;--pico-ins-color:#1d6a54;--pico-del-color:#883935;--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:#f3f5f7;--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:#fbfcfc;--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:#b86a6b;--pico-form-element-invalid-active-border-color:#c84f48;--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#4c9b8a;--pico-form-element-valid-active-border-color:#279977;--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:#fbfcfc;--pico-dropdown-background-color:#fff;--pico-dropdown-border-color:#eff1f4;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#eff1f4;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 155, 138)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200, 79, 72)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");color-scheme:light}:root:not([data-theme=dark]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),[data-theme=light] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:root:not([data-theme]){--pico-background-color:#13171f;--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 9, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 9, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 9, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 9, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 9, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 9, 12, 0.06),0 0 0 0.0625rem rgba(7, 9, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:#ce7e7b;--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:#1a1f28;--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:#1c212c;--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:#1a1f28;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:#964a50;--pico-form-element-invalid-active-border-color:#b7403b;--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:#16896a;--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:#1a1f28;--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(8, 9, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(150, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");color-scheme:dark}:root:not([data-theme]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}:root:not([data-theme]) details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}:root:not([data-theme]) [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}}[data-theme=dark]{--pico-background-color:#13171f;--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 9, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 9, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 9, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 9, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 9, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 9, 12, 0.06),0 0 0 0.0625rem rgba(7, 9, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:#ce7e7b;--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:#1a1f28;--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:#1c212c;--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:#1a1f28;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:#964a50;--pico-form-element-invalid-active-border-color:#b7403b;--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:#16896a;--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:#1a1f28;--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(8, 9, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(150, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");color-scheme:dark}[data-theme=dark] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}[data-theme=dark] details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}[data-theme=dark] [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{padding-block:var(--pico-block-spacing-vertical)}section{margin-bottom:var(--pico-block-spacing-vertical)}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--pico-spacing);padding-left:var(--pico-spacing)}@media (min-width:576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){.container{max-width:700px}}@media (min-width:1024px){.container{max-width:950px}}@media (min-width:1280px){.container{max-width:1200px}}@media (min-width:1536px){.container{max-width:1450px}}.grid{grid-column-gap:var(--pico-grid-column-gap);grid-row-gap:var(--pico-grid-row-gap);display:grid;grid-template-columns:1fr}@media (min-width:768px){.grid{grid-template-columns:repeat(auto-fit,minmax(0%,1fr))}}.grid>*{min-width:0}.overflow-auto{overflow:auto}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}:where(a:not([role=button])).secondary,[role=link].secondary{--pico-color:var(--pico-secondary);--pico-underline:var(--pico-secondary-underline)}:where(a:not([role=button])).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-underline:var(--pico-secondary-hover-underline)}:where(a:not([role=button])).contrast,[role=link].contrast{--pico-color:var(--pico-contrast);--pico-underline:var(--pico-contrast-underline)}:where(a:not([role=button])).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-underline:var(--pico-contrast-hover-underline)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}:is(button,[type=submit],[type=button],[role=button]).secondary,[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}:is(button,[type=submit],[type=button],[role=button]).secondary:focus,:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:is(button,[type=submit],[type=button],[role=button]).contrast{--pico-background-color:var(--pico-contrast-background);--pico-border-color:var(--pico-contrast-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-contrast-hover-background);--pico-border-color:var(--pico-contrast-hover-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:focus,:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}:is(button,[type=submit],[type=button],[role=button]).outline,[type=reset].outline{--pico-background-color:transparent;--pico-color:var(--pico-primary);--pico-border-color:var(--pico-primary)}:is(button,[type=submit],[type=button],[role=button]).outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:transparent;--pico-color:var(--pico-primary-hover);--pico-border-color:var(--pico-primary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary,[type=reset].outline{--pico-color:var(--pico-secondary);--pico-border-color:var(--pico-secondary)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-border-color:var(--pico-secondary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast{--pico-color:var(--pico-contrast);--pico-border-color:var(--pico-contrast)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-border-color:var(--pico-contrast-hover)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-size:.875em;font-family:var(--pico-font-family)}pre code{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset,.grid)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--pico-icon-position:0.75rem;--pico-icon-width:1rem;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}details.dropdown{position:relative;border-bottom:none}details.dropdown summary::after,details.dropdown>a::after,details.dropdown>button::after{display:block;width:1rem;height:calc(1rem * var(--pico-line-height,1.5));margin-inline-start:.25rem;float:right;transform:rotate(0) translateX(.2rem);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}nav details.dropdown{margin-bottom:0}details.dropdown summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-form-element-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-form-element-background-color);color:var(--pico-form-element-placeholder-color);line-height:inherit;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}details.dropdown summary:not([role]):active,details.dropdown summary:not([role]):focus{border-color:var(--pico-form-element-active-border-color);background-color:var(--pico-form-element-active-background-color)}details.dropdown summary:not([role]):focus{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}details.dropdown summary:not([role]):focus-visible{outline:0}details.dropdown summary:not([role])[aria-invalid=false]{--pico-form-element-border-color:var(--pico-form-element-valid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-valid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-valid-focus-color)}details.dropdown summary:not([role])[aria-invalid=true]{--pico-form-element-border-color:var(--pico-form-element-invalid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-invalid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-invalid-focus-color)}nav details.dropdown{display:inline;margin:calc(var(--pico-nav-element-spacing-vertical) * -1) 0}nav details.dropdown summary::after{transform:rotate(0) translateX(0)}nav details.dropdown summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2);padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav details.dropdown summary:not([role]):focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}details.dropdown summary+ul{display:flex;z-index:99;position:absolute;left:0;flex-direction:column;width:100%;min-width:-moz-fit-content;min-width:fit-content;margin:0;margin-top:var(--pico-outline-width);padding:0;border:var(--pico-border-width) solid var(--pico-dropdown-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-dropdown-background-color);box-shadow:var(--pico-dropdown-box-shadow);color:var(--pico-dropdown-color);white-space:nowrap;opacity:0;transition:opacity var(--pico-transition),transform 0s ease-in-out 1s}details.dropdown summary+ul[dir=rtl]{right:0;left:auto}details.dropdown summary+ul li{width:100%;margin-bottom:0;padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);list-style:none}details.dropdown summary+ul li:first-of-type{margin-top:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown summary+ul li:last-of-type{margin-bottom:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown summary+ul li a{display:block;margin:calc(var(--pico-form-element-spacing-vertical) * -.5) calc(var(--pico-form-element-spacing-horizontal) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);overflow:hidden;border-radius:0;color:var(--pico-dropdown-color);text-decoration:none;text-overflow:ellipsis}details.dropdown summary+ul li a:active,details.dropdown summary+ul li a:focus,details.dropdown summary+ul li a:focus-visible,details.dropdown summary+ul li a:hover,details.dropdown summary+ul li a[aria-current]:not([aria-current=false]){background-color:var(--pico-dropdown-hover-background-color)}details.dropdown summary+ul li label{width:100%}details.dropdown summary+ul li:has(label):hover{background-color:var(--pico-dropdown-hover-background-color)}details.dropdown[open] summary{margin-bottom:0}details.dropdown[open] summary+ul{transform:scaleY(1);opacity:1;transition:opacity var(--pico-transition),transform 0s ease-in-out 0s}details.dropdown[open] summary::before{display:block;z-index:1;position:fixed;width:100vw;height:100vh;inset:0;background:0 0;content:"";cursor:default}label>details.dropdown{margin-top:calc(var(--pico-spacing) * .25)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select,[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:"";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:root{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;-webkit-backdrop-filter:var(--pico-modal-overlay-backdrop-filter);backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog article{max-width:510px}}@media (min-width:768px){dialog article{max-width:700px}}dialog article>header>*{margin-bottom:0}dialog article>header .close,dialog article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog article>footer{text-align:right}dialog article>footer [role=button],dialog article>footer button{margin-bottom:0}dialog article>footer [role=button]:not(:first-of-type),dialog article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog article .close,dialog article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog article .close:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),dialog article :is(a,button)[rel=prev]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}.modal-is-open{padding-right:var(--pico-scrollbar-width,0);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto;touch-action:auto}:where(.modal-is-opening,.modal-is-closing) dialog,:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening,.modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{-webkit-backdrop-filter:none;backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"​"}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}} \ No newline at end of file diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Fixme.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Fixme.hs deleted file mode 100644 index e8c02fa9..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Fixme.hs +++ /dev/null @@ -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 - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Manifest.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Manifest.hs deleted file mode 100644 index 45fb8fd5..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Manifest.hs +++ /dev/null @@ -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) - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Prelude.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Prelude.hs deleted file mode 100644 index 80d4cca5..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Prelude.hs +++ /dev/null @@ -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 - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State.hs deleted file mode 100644 index c6612a1c..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State.hs +++ /dev/null @@ -1,1153 +0,0 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE ViewPatterns #-} -module HBS2.Git.DashBoard.State - ( module HBS2.Git.DashBoard.State - , Only(..) - , transactional - ) where - -import HBS2.Git.DashBoard.Prelude -import HBS2.Git.DashBoard.Types - -import HBS2.Hash - -import HBS2.Git.Data.RepoHead -import HBS2.Git.Data.Tx.Git -import HBS2.Git.Local -import HBS2.Git.Local.CLI -import HBS2.System.Dir - -import DBPipe.SQLite hiding (insert) -import DBPipe.SQLite qualified as S -import DBPipe.SQLite.Generic as G - -import Data.List.Split (chunksOf) -import Data.Aeson as Aeson -import Data.ByteString.Lazy.Char8 qualified as LBS8 -import Data.ByteString.Lazy (ByteString) -import Lucid.Base -import Data.Text qualified as Text -import Data.Word -import Data.Either -import Data.List qualified as List -import Data.Map qualified as Map -import Data.Map (Map) -import Data.HashMap.Strict qualified as HM -import Data.HashSet qualified as HS -import System.FilePath -import System.Directory - -import Skylighting.Core qualified as Sky -import Skylighting qualified as Sky - -data RepoListPred = - RepoListPred - { _repoListByLww :: Maybe (LWWRefKey 'HBS2Basic) - , _repoListLimit :: Maybe Int - } - -makeLenses 'RepoListPred - -instance Semigroup RepoListPred where - (<>) _ b = mempty & set repoListByLww (view repoListByLww b) - & set repoListLimit (view repoListLimit b) - -instance Monoid RepoListPred where - mempty = RepoListPred Nothing Nothing - - -evolveDB :: DashBoardPerks m => DBPipeM m () -evolveDB = do - - ddl [qc| - create table if not exists repo - ( lww text not null - , primary key (lww) - ) - |] - - - ddl [qc| - create table if not exists repochannel - ( lww text not null - , channel text not null - , primary key (lww,channel) - ) - |] - - ddl [qc| - create table if not exists brief - ( lww text not null - , brief text not null - , primary key (lww) - ) - |] - - ddl [qc| - create table if not exists name - ( lww text not null - , name text not null - , primary key (lww) - ) - |] - - - createRepoHeadTable - createRepoListView - - ddl [qc| - create table if not exists processed - ( hash text not null - , primary key (hash) - ) - |] - - createRepoTreeIndexTable - createRepoBlobIndexTable - createRepoCommitTable - createForksTable - - ddl [qc| - create table if not exists object - ( o text not null - , w integer not null - , k text not null - , v text not null - , nonce text null - , primary key (o,k) - ) - |] - -instance ToField GitHash where - toField x = toField $ show $ pretty x - -instance FromField GitHash where - fromField = fmap fromString . fromField @String - -instance ToField HashRef where - toField x = toField $ show $ pretty x - -instance FromField HashRef where - fromField = fmap (fromString @HashRef) . fromField @String - -instance Pretty (AsBase58 (PubKey 'Sign s)) => ToField (LWWRefKey s) where - toField x = toField $ show $ pretty (AsBase58 x) - -instance Pretty (AsBase58 (PubKey 'Sign s)) => ToField (RefLogKey s) where - toField x = toField $ show $ pretty (AsBase58 x) - -instance IsRefPubKey s => FromField (RefLogKey s) where - fromField = fmap (fromString @(RefLogKey s)) . fromField @String - -instance FromField (LWWRefKey HBS2Basic) where - fromField = fmap fromString . fromField @String - - -newtype TxHash = TxHash HashRef - deriving stock (Generic) - deriving newtype (ToField) - - -newtype RepoHeadTx = RepoHeadTx HashRef - deriving stock (Generic) - deriving newtype (ToField,FromField,Pretty) - -instance Serialise RepoHeadTx - -newtype RepoName = RepoName Text - deriving stock (Eq,Show,Generic) - deriving newtype (ToField,FromField,ToHtml,IsString,Pretty) - -newtype RepoBrief = RepoBrief Text - deriving stock (Generic) - deriving newtype (ToField,FromField) - - -newtype RepoForks = RepoForks Int - deriving stock (Generic,Data) - deriving newtype (ToField,FromField,Show,Pretty,Num,Eq,Ord) - -newtype RepoCommitsNum = RepoCommitsNum Int - deriving stock (Generic,Data) - deriving newtype (ToField,FromField,Show,Pretty) - -newtype RepoLww = RepoLww (LWWRefKey 'HBS2Basic) - deriving stock (Generic,Ord,Eq) - deriving newtype (ToField,FromField,Pretty,Hashable) - -instance Show RepoLww where - show (RepoLww x) = show $ parens $ "RepoLww" <+> pretty x - -newtype RepoLwwSeq = RepoLwwSeq Integer - deriving stock (Generic) - deriving newtype (ToField,FromField,Pretty) - -newtype RepoChannel = RepoChannel MyRefChan - -newtype RefChanField = RefChanField MyRefChan - deriving stock (Generic) - -newtype RepoHeadRef = RepoHeadRef HashRef - deriving stock (Generic) - deriving newtype (ToField,FromField) - -instance Serialise RepoHeadRef - - -newtype RepoHeadSeq = RepoHeadSeq Word64 - deriving stock (Generic) - deriving newtype (ToField,FromField,Integral,Real,Ord,Eq,Num,Enum) - -newtype RepoRefLog = RepoRefLog (RefLogKey 'HBS2Basic) - deriving stock (Generic) - deriving newtype (ToField,FromField,Pretty,Serialise) - -newtype RepoHeadGK0 = RepoHeadGK0 (Maybe HashRef) - deriving stock (Generic) - deriving newtype (ToField,FromField) - -newtype Base58Field a = Base58Field { fromBase58Field :: a } - deriving stock (Eq,Ord,Generic) - - -instance ToField RepoChannel where - toField (RepoChannel x) = toField $ show $ pretty (AsBase58 x) - -instance ToField RefChanField where - toField (RefChanField x) = toField $ show $ pretty (AsBase58 x) - -instance FromField RefChanField where - fromField w = fromField @String w - >>= maybe (fail "invalid key") (pure . RefChanField) . fromStringMay - -data TxProcessedTable -data RepoTable -data RepoChannelTable -data RepoNameTable -data RepoBriefTable -data RepoCommitTable - -instance HasTableName RepoChannelTable where - tableName = "repochannel" - -instance HasTableName RepoTable where - tableName = "repo" - -instance HasTableName RepoNameTable where - tableName = "name" - -instance HasTableName RepoBriefTable where - tableName = "brief" - -instance HasTableName TxProcessedTable where - tableName = "processed" - -instance HasTableName RepoCommitTable where - tableName = "repocommit" - -instance HasColumnName TxHash where - columnName = "hash" - -instance HasColumnName RepoLww where - columnName = "lww" - -instance HasColumnName RepoLwwSeq where - columnName = "lwwseq" - -instance HasColumnName RepoName where - columnName = "name" - -instance HasColumnName RepoBrief where - columnName = "brief" - -instance HasColumnName RepoForks where - columnName = "forks" - -instance HasColumnName RepoCommitsNum where - columnName = "kommits" - -instance HasColumnName RepoRefLog where - columnName = "reflog" - -instance HasColumnName RepoChannel where - columnName = "channel" - -instance HasColumnName RepoCommit where - columnName = "kommit" - -instance HasPrimaryKey TxProcessedTable where - primaryKey = [G.columnName @TxHash] - -instance HasPrimaryKey RepoChannelTable where - primaryKey = [G.columnName @RepoLww, G.columnName @RepoChannel] - -instance HasPrimaryKey RepoTable where - primaryKey = [G.columnName @RepoLww] - -instance HasPrimaryKey RepoNameTable where - primaryKey = [G.columnName @RepoLww] - -instance HasPrimaryKey RepoBriefTable where - primaryKey = [G.columnName @RepoLww] - -instance HasPrimaryKey RepoCommitTable where - primaryKey = [G.columnName @RepoLww, G.columnName @RepoCommit] - -pattern PRefChan :: MyRefChan -> Syntax C -pattern PRefChan s <- ListVal [ SymbolVal "refchan" , asRefChan -> Just s ] - -asRefChan :: Syntax C -> Maybe MyRefChan -asRefChan = \case - LitStrVal s -> fromStringMay @MyRefChan (Text.unpack s) - _ -> Nothing - -getIndexEntries :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m [MyRefChan] -getIndexEntries = do - pure mempty - -- conf <- getConf - -- pure [ s | ListVal [ SymbolVal "index", PRefChan s] <- conf ] - - -data NiceTS = NiceTS - -data RepoListItem = - RepoListItem - { rlRepoLww :: RepoLww - , rlRepoSeq :: RepoHeadSeq - , rlRepoHead :: RepoHeadRef - , rlRepoTx :: RepoHeadTx - , rlRepoName :: RepoName - , rlRepoBrief :: RepoBrief - , rlRepoGK0 :: RepoHeadGK0 - , rlRepoForks :: RepoForks - , rlRepoCommits :: RepoCommitsNum - } - deriving stock (Generic) - --- deriving instance Data RepoListItem via Generically RepoListItem - -rlRepoLwwAsText :: SimpleGetter RepoListItem Text -rlRepoLwwAsText = - to \RepoListItem{..} -> do - Text.pack $ show $ pretty $ rlRepoLww - -instance FromRow RepoListItem - - - -selectRepoList :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoListPred -> m [RepoListItem] -selectRepoList pred = fmap fixName <$> withState do - - let onLww = maybe1 (view repoListByLww pred) mempty $ \w -> [("r.lww = ?", w)] - let claus = onLww - - let where_ | List.null claus = "true" - | otherwise = Text.intercalate " and " (fmap fst claus) - - let limit_ = case view repoListLimit pred of - Nothing -> mempty - Just n -> show $ "limit" <+> pretty n - - let params = fmap snd claus - - let sql = [qc| - select r.lww - , r.seq - , r.repohead - , r.tx - , r.name - , r.brief - , r.gk0 - , r.forks - , r.kommits - from repolistview r - where {where_} - {limit_} - |] - - debug $ yellow "selectRepoList" <+> pretty sql - - select @RepoListItem sql params - where - fixName x@RepoListItem{..} | Text.length (coerce rlRepoName) < 3 = x { rlRepoName = fixed } - | otherwise = x - where fixed = Text.pack (show $ pretty (coerce @_ @(LWWRefKey 'HBS2Basic) rlRepoLww) ) & RepoName - -createRepoListView :: DashBoardPerks m => DBPipeM m () -createRepoListView = do - ddl [qc| -drop view if exists repolistview - |] - - ddl [qc| -create view repolistview as - -with repolist as ( - select - r.lww, - 0 as lwwseq, - null as reflog, - 0 as seq, - null as repohead, - null as tx, - coalesce(n.name, r.lww) as name, - coalesce(b.brief, '') as brief, - null as gk0 - from repo r - left join name n on r.lww = n.lww - left join brief b on r.lww = b.lww - union - select - lww, - lwwseq, - reflog, - seq, - repohead, - tx, - name, - brief, - gk0 - from repohead -), -ranked_repos as ( - select - lww, - lwwseq, - reflog, - seq, - repohead, - tx, - name, - brief, - gk0, - row_number() over (partition by lww order by lwwseq desc, seq desc) as rn - from repolist - order by seq desc -) - -select lww - , lwwseq - , reflog - , seq - , repohead - , tx - , name - , brief - , gk0 - , (select count(1) from fork f where f.a = ranked_repos.lww) as forks - , (select count(distinct(kommit)) from repocommit r where r.lww = ranked_repos.lww) as kommits -from ranked_repos -where rn = 1; - |] - - -createForksTable :: DashBoardPerks m => DBPipeM m () -createForksTable = do - ddl [qc| - create table if not exists fork - ( a text not null - , b text not null - , primary key (a,b) - ) - |] - -createRepoHeadTable :: DashBoardPerks m => DBPipeM m () -createRepoHeadTable = do - ddl [qc| - create table if not exists repohead - ( lww text not null - , lwwseq integer not null - , reflog text not null - , repohead text not null - , tx text not null - , seq integer not null - , gk0 text null - , name text - , brief text - , primary key (lww,lwwseq,repohead) - ) - |] - - ddl [qc| - create table if not exists repoheadfixme - ( lww text not null - , lwwseq integer not null - , refchan text not null - , primary key (lww, lwwseq) - ) - |] - -data RepoHeadTable - -instance HasTableName RepoHeadTable where - tableName = "repohead" - -instance HasPrimaryKey RepoHeadTable where - primaryKey = ["lww", "lwwseq", "repohead"] - -instance HasColumnName RepoHeadRef where - columnName = "repohead" - -instance HasColumnName RepoHeadSeq where - columnName = "seq" - -instance HasColumnName RepoHeadGK0 where - columnName = "gk0" - -instance HasColumnName RepoHeadTx where - columnName = "tx" - - -insertRepoHead :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> RepoLwwSeq - -> RepoRefLog - -> RepoHeadTx - -> RepoHeadRef - -> RepoHead - -> DBPipeM m () -insertRepoHead lww lwwseq rlog tx rf rh = do - insert @RepoHeadTable $ onConflictIgnore @RepoHeadTable - ( RepoLww lww - , lwwseq - , rlog - , rf - , tx - , RepoHeadSeq (_repoHeadTime rh) - , RepoHeadGK0 (_repoHeadGK0 rh) - , RepoName (_repoHeadName rh) - , RepoBrief (_repoHeadBrief rh) - ) - - pure () - - -insertRepoFixme :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> RepoLwwSeq - -> MyRefChan - -> DBPipeM m () -insertRepoFixme lww lwwseq r = do - S.insert [qc| - insert into repoheadfixme (lww, lwwseq, refchan) values(?,?,?) - on conflict (lww, lwwseq) do update set refchan = excluded.refchan - |] - (lww, lwwseq, RefChanField r) - -selectRepoFixme :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => m [(RepoLww, MyRefChan)] - -selectRepoFixme = do - let sql = [qc| - select lww, refchan from (select lww, refchan, max(lwwseq) from repoheadfixme group by lww) - |] - withState $ select_ @_ @(RepoLww, RefChanField) sql - <&> fmap (over _2 coerce) - --- FIXME: what-if-two-repo-shares-one-reflog? -selectLwwByRefLog :: (DashBoardPerks m, MonadReader DashBoardEnv m) => RepoRefLog -> m (Maybe RepoLww) -selectLwwByRefLog rlog = withState do - select [qc|select lww from repolistview where reflog = ?|] (Only rlog) - <&> listToMaybe . fmap fromOnly - -selectRefLogs :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m [RepoRefLog] -selectRefLogs = withState do - select_ [qc|select distinct(reflog) from repolistview|] <&> fmap fromOnly - --- TODO: too-much-data-in-tree-index --- для навигации по дереву, если нам не нужно --- выходить на верхний уровень -- нам не нужно --- знать коммит для каждого дерева. таким образом, --- если убрать коммит -- вариативность будет на порядок --- меньше, но это повлечёт последствия для навигации. --- сейчас уже 200K записей на 4K коммитов, нехорошо. --- ОЧЕНЬ НЕХОРОШО. Однако, если удалить kommit из --- таблицы tree, там начинает выполняться большой рекурсивный --- запрос. С колонкой kommit мы сокращаем там выборку. --- В принципе, можно туда ввести ключ lww, тогда выборка --- будет ограничиваться только всеми деревьями проекта. --- С этим полем в таблице будет гораздо меньше ключей, чем --- с каждым коммитом. -createRepoTreeIndexTable :: (DashBoardPerks m) => DBPipeM m () -createRepoTreeIndexTable = do - ddl [qc| - create table if not exists tree - ( parent text not null - , tree text not null - , kommit text not null - , level int not null - , path text not null - , primary key (parent,tree,kommit) - ) - |] - - -createRepoCommitTable :: (DashBoardPerks m) => DBPipeM m () -createRepoCommitTable = do - ddl [qc| - create table if not exists repocommit - ( lww text not null - , kommit text not null - , primary key (lww,kommit) - ) - |] - - -isProcessed :: (MonadIO m) => HashRef -> DBPipeM m Bool -isProcessed href = do - select @(Only Int) [qc|select 1 from processed where hash = ? limit 1|] (Only href) - <&> not . List.null - -insertProcessed :: (DashBoardPerks m) => HashRef -> DBPipeM m () -insertProcessed href = do - S.insert [qc| - insert into processed (hash) - values(?) - on conflict(hash) do nothing - |] (Only href) - - -newtype RepoCommit = RepoCommit GitHash - deriving newtype (FromField,ToField,Pretty) - -newtype TreeCommit = TreeCommit GitHash - deriving newtype (FromField,ToField,Pretty) - -newtype TreeParent = TreeParent GitHash - deriving newtype (FromField,ToField,Pretty) - - -newtype TreeTree = TreeTree GitHash - deriving newtype (FromField,ToField,Pretty) - -newtype TreeLevel = TreeLevel Int - deriving newtype (FromField,ToField,Pretty,Num,Enum,Real,Integral,Ord,Eq) - -newtype TreePath = TreePath FilePath - deriving newtype (FromField,ToField,Pretty) - -insertTree :: (DashBoardPerks m) - => (TreeCommit,TreeParent,TreeTree,TreeLevel,TreePath) - -> DBPipeM m () -insertTree (commit,parent,tree,level,path) = do - S.insert [qc| - insert into tree (parent,tree,kommit,level,path) - values (?,?,?,?,?) - on conflict (parent,tree,kommit) - do nothing - |] (parent,tree,commit,level,path) - -selectParentTree :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => TreeCommit - -> TreeTree - -> m (Maybe TreeParent) -selectParentTree co me = withState do - select [qc|select parent from tree where tree = ? and kommit = ?|] (me,co) - <&> listToMaybe . fmap fromOnly - -{- HLINT ignore "Functor law" -} - - - -createRepoBlobIndexTable :: (DashBoardPerks m) => DBPipeM m () -createRepoBlobIndexTable = do - ddl [qc| - create table if not exists blob - ( hash text not null - , name text not null - , size int not null - , syntax text - , primary key (hash) - ) - |] - - -newtype BlobSyn = BlobSyn (Maybe Text) - deriving newtype (FromField,ToField,Pretty,Eq,Ord) - -newtype BlobName = BlobName FilePath - deriving newtype (FromField,ToField,Pretty) - -newtype BlobHash = BlobHash GitHash - deriving newtype (FromField,ToField,Pretty) - -newtype BlobSize = BlobSize Integer - deriving newtype (FromField,ToField,Pretty,Num,Enum,Eq,Ord) - - -data BlobInfo = - BlobInfo - { blobHash :: BlobHash - , blobName :: BlobName - , blobSize :: BlobSize - , blobSyn :: BlobSyn - } - deriving stock (Generic) - -instance FromRow BlobInfo - -type TreeLocator = [(TreeParent, TreeTree, TreeLevel, TreePath)] - - -insertBlob :: DashBoardPerks m - => (BlobHash, BlobName, BlobSize, BlobSyn) - -> DBPipeM m () -insertBlob (h, n, size, syn) = do - S.insert [qc| - insert into blob (hash, name, size, syntax) - values (?,?,?,?) - on conflict (hash) - do update set name = excluded.name - , size = excluded.size - , syntax = excluded.syntax - where blob.name != excluded.name - or blob.size != excluded.size - or blob.syntax != excluded.syntax - |] (h, n, size, syn) - - --- insertBlob :: DashBoardPerks m --- => (BlobHash, BlobName, BlobSize, BlobSyn) --- -> DBPipeM m () --- insertBlob (h,n,size,syn) = do --- S.insert [qc| --- insert into blob (hash,name,size,syntax) --- values (?,?,?,?) --- on conflict (hash) --- do update set name = excluded.name --- , size = excluded.size --- , syntax = excluded.syntax --- |] (h,n,size,syn) - - -selectBlobInfo :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => BlobHash - -> m (Maybe BlobInfo) -selectBlobInfo what = withState do - select [qc| - select hash,name,size,syntax - from blob - where hash = ? - |] (Only what) - <&> listToMaybe - -selectTreeLocator :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => TreeCommit - -> TreeTree - -> m TreeLocator - -selectTreeLocator kommit tree = withState do - - let sql = [qc| -WITH RECURSIVE ParentTree AS ( - SELECT parent, tree, kommit, level, path - FROM tree - WHERE tree = ? AND kommit = ? - - UNION ALL - - SELECT t.parent, t.tree, t.kommit, t.level, t.path - FROM tree t - JOIN ParentTree pt ON t.tree = pt.parent AND t.kommit = pt.kommit - WHERE t.kommit = ? -) -SELECT parent, tree, level, path FROM ParentTree -ORDER BY level -|] - - select sql (tree, kommit, kommit) - - -pattern TreeHash :: GitHash -> LBS8.ByteString -pattern TreeHash hash <- (LBS8.words -> (_ : (fromStringMay . LBS8.unpack -> Just hash) : _)) - -readBlob :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> BlobHash - -> m ByteString - -readBlob repo hash = do - - dir <- repoDataPath repo - - gitRunCommand [qc|git --git-dir {dir} cat-file blob {pretty hash}|] - <&> fromRight mempty - - -updateForks :: (MonadIO m, MonadReader DashBoardEnv m) => LWWRefKey 'HBS2Basic -> DBPipeM m () -updateForks lww = do - - S.insert [qc| - insert into fork (a,b) - select distinct r0.lww - , r1.lww - from repocommit r0 join repocommit r1 on r0.kommit = r1.kommit and r0.lww <> r1.lww - where r0.lww = ? - on conflict (a,b) do nothing - |] (Only lww) - - pure () - -checkCommitProcessed :: (MonadIO m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic -> GitHash -> m Bool -checkCommitProcessed lww co = withState do - select [qc|select 1 from repocommit where lww = ? and kommit = ?|] (lww, co) - <&> listToMaybe @(Only Int) <&> isJust - - -listCommitsProcessed :: (MonadIO m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic -> m [GitHash] -listCommitsProcessed lww = withState do - select [qc|select kommit from repocommit where lww = ?|] (Only lww) - <&> fmap fromOnly - -listCommits :: (MonadUnliftIO m, MonadReader DashBoardEnv m) - => LWWRefKey HBS2Basic -> m [GitHash] -listCommits lww = do - dir <- repoDataPath lww - gitRunCommand [qc|git --git-dir {dir} rev-list --all|] - <&> fromRight mempty - <&> mapMaybe (headMay . LBS8.words) . LBS8.lines - <&> mapMaybe (fromStringMay @GitHash . LBS8.unpack) - - -getTreeRecursive :: (MonadUnliftIO m,MonadReader DashBoardEnv m) - => LWWRefKey HBS2Basic - -> GitHash - -> m (Map [FilePath] GitHash,[(FilePath, (GitHash, Integer, Maybe Text))]) -getTreeRecursive lww co = do - - let syntaxMap = Sky.defaultSyntaxMap - - dir <- repoDataPath lww - items <- gitRunCommand [qc|git --git-dir {dir} ls-tree -l -r -t {pretty co}|] - <&> fromRight mempty - <&> fmap LBS8.words . LBS8.lines - <&> mapMaybe \case - [_,"tree",h,_,n] -> - (reverse $ splitDirectories $ LBS8.unpack n,) <$> fmap Right (fromStringMay @GitHash (LBS8.unpack h)) - - [_,"blob",h,size,n] -> do - let fn = headMay (reverse $ splitDirectories $ LBS8.unpack n) - <&> List.singleton - - let ha = fromStringMay @GitHash (LBS8.unpack h) - let sz = readMay @Integer (LBS8.unpack size) - - let syn = Sky.syntaxesByFilename syntaxMap (LBS8.unpack n) - & headMay - <&> Text.toLower . Sky.sName - - (,) <$> fn <*> fmap Left ( (,,) <$> ha <*> sz <*> pure syn ) - - _ -> Nothing - - let trees = Map.fromList [ (k,v) | (k,Right v) <- items ] - let blobs = [ (k,v) | ([k],Left v) <- items ] - pure (trees, blobs) - -getRootTree :: (MonadUnliftIO m, MonadReader DashBoardEnv m) - => LWWRefKey HBS2Basic -> GitHash -> m (Maybe GitHash) -getRootTree lww co = do - dir <- repoDataPath lww - let cmd = [qc|git --git-dir {dir} cat-file commit {pretty co}|] - - gitRunCommand cmd - <&> fromRight mempty - <&> LBS8.lines - <&> \case - (TreeHash ha : _) -> Just ha - _ -> Nothing - -updateRepoData :: (MonadReader DashBoardEnv m, MonadUnliftIO m) - => LWWRefKey HBS2Basic -> GitHash -> DBPipeM m () -updateRepoData lww co = do - - env <- ask - - void $ runMaybeT do - - root <- lift (lift (getRootTree lww co)) >>= toMPlus - (trees, blobs) <- lift $ lift $ getTreeRecursive lww co - - -- lift $ addJob $ liftIO $ withDashBoardEnv env do - - -- lift $ withState do - lift do - insert @RepoCommitTable $ - onConflictIgnore @RepoCommitTable (RepoLww lww, RepoCommit co) - - for_ blobs $ \(fn, (hash, size, syn)) -> do - insertBlob (BlobHash hash, BlobName fn, BlobSize size, BlobSyn syn) - - for_ (Map.toList trees) $ \(t,h0) -> do - - case t of - [x] -> insertTree (TreeCommit co,TreeParent root,TreeTree h0,1,TreePath x) - _ -> pure () - - let child = tailSafe t - debug $ red "TREE-REL:" <+> pretty t - let parent = Map.lookup child trees - - for_ parent $ \p -> do - debug $ red "FOUND SHIT:" <+> pretty (h0,p) - insertTree ( TreeCommit co - , TreeParent p - , TreeTree h0 - , TreeLevel (length t) - , TreePath (headDef "" t) - ) - - -- updateForks lww - -buildSingleCommitTreeIndex :: ( MonadUnliftIO m - , DashBoardPerks m - , MonadReader DashBoardEnv m - ) - => LWWRefKey 'HBS2Basic - -> GitHash - -> m () -buildSingleCommitTreeIndex lww co = do - - env <- ask - ignoreCaches <- getIgnoreCaches - - void $ runMaybeT do - done <- checkCommitProcessed lww co - let skip = done && not ignoreCaches - guard (not skip) - lift $ withState $ transactional $ do - updateRepoData lww co - updateForks lww - -buildCommitTreeIndex :: ( MonadUnliftIO m - , DashBoardPerks m - , MonadReader DashBoardEnv m - ) - => LWWRefKey 'HBS2Basic - -> m () -buildCommitTreeIndex lww = do - - - debug $ red "buildCommitTreeIndex" <+> pretty lww - - env <- ask - - ignoreCaches <- getIgnoreCaches - - doneCommits <- listCommitsProcessed lww <&> HS.fromList - - commits <- listCommits lww <&> filter (not . flip HS.member doneCommits) - let chunks = chunksOf 100 commits - - for_ chunks $ \chunk -> do - -- addJob $ withDashBoardEnv env do - withState $ transactional do - for_ chunk $ \co -> do - updateRepoData lww co - - unless (List.null chunks) do - withState $ transactional $ do updateForks lww - - -- FIXME: check-names-with-spaces - -selectRepoForks :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> m [RepoListItem] -selectRepoForks lww = withState do - let cols = columnListPart (AllColumns @RepoListItem) & fromSQL - let sql = [qc| select {cols} - from repolistview v join fork f on v.lww = f.b - where f.a = ? - |] - - debug $ yellow "selectRepoForks" <+> pretty sql <+> pretty lww - select sql (Only (RepoLww lww)) - -gitShowTree :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> GitHash - -> m [(GitObjectType, GitHash, Text)] -gitShowTree what hash = do - path <- repoDataPath what - let cmd = [qc|git --git-dir {path} ls-tree {show $ pretty hash}|] - - -- FIXME: extract-method - gitRunCommand cmd - <&> fromRight mempty - <&> LBS8.lines - <&> fmap LBS8.words - <&> mapMaybe \case - [_,tp,h,name] -> do - (,,) <$> fromStringMay (LBS8.unpack tp) - <*> fromStringMay (LBS8.unpack h) - <*> pure (fromString (LBS8.unpack name)) - - _ -> Nothing - - -gitShowRefs :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => LWWRefKey 'HBS2Basic - -> m [(GitRef, GitHash)] - -gitShowRefs what = do - path <- repoDataPath what - let cmd = [qc|git --git-dir {path} show-ref|] - - sto <- asks _sto - - fromMaybe mempty <$> runMaybeT do - - (_,hd) <- lift (selectRepoList (mempty & set repoListByLww (Just what) & set repoListLimit (Just 1))) - <&> listToMaybe - >>= toMPlus - <&> rlRepoTx - >>= readRepoHeadFromTx sto . coerce - >>= toMPlus - - pure $ view repoHeadRefs hd - - -insertOWKV :: (DashBoardPerks m, ToJSON a) - => Text - -> Maybe Integer - -> Text - -> a - -> DBPipeM m () -insertOWKV o w k v = do - - let sql = [qc| - - insert into object (o, w, k, v) - values (?, ?, ?, cast (? as text)) - on conflict (o, k) - do update set - v = case - when excluded.w > object.w then excluded.v - else object.v - end, - w = case - when excluded.w > object.w then excluded.w - else object.w - end - |] - - t <- maybe1 w (round <$> liftIO getPOSIXTime) pure - - S.insert sql (o,t,k,Aeson.encode v) - - -insertOption :: ( DashBoardPerks m - , MonadReader DashBoardEnv m - , Pretty a - , Serialise a) - => Text - -> a - -> m () -insertOption key value = do - w <- liftIO getPOSIXTime <&> fromIntegral . round - let o = hashObject @HbSync (serialise ("option", key)) & pretty & show - let v = show $ pretty v - withState $ transactional do - insertOWKV (fromString o) (Just w) "$type" "option" - insertOWKV (fromString o) (Just w) "name" key - insertOWKV (fromString o) (Just w) "value" (fromString v) - - -insertFixmeAllowed :: ( DashBoardPerks m - , MonadReader DashBoardEnv m - ) - => RepoRefLog - -> m () -insertFixmeAllowed reflog = do - let o = hashObject @HbSync (serialise ("fixme-allowed", reflog)) & pretty & show - let v = show $ pretty reflog - withState $ transactional do - insertOWKV (fromString o) mzero "$type" "fixme-allowed" - insertOWKV (fromString o) mzero "value" v - -deleteFixmeAllowed :: ( DashBoardPerks m - , MonadReader DashBoardEnv m - ) - => m () -deleteFixmeAllowed = do - - let sql = [qc| - with - s1 as ( - select o from object where k = '$type' and json_extract(v, '$') = 'fixme-allowed' - ) - delete from object where o in (select o from s1) - |] - - withState $ S.insert_ sql - -checkFixmeAllowed :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => RepoLww - -> m Bool - -checkFixmeAllowed r = do - - let sql = [qc| - with - s1 as ( - select o from object where k = '$type' and json_extract(v, '$') = 'fixme-allowed' - ) - select 1 - from s1 join object o on s1.o = o.o - where o.k = 'value' and json_extract(o.v, '$') = ? - limit 1; - |] - - w <- withState $ select @(Only Int) sql (Only r) - - pure $ not $ List.null w - -selectRepoFixmeRefChan :: (DashBoardPerks m, MonadReader DashBoardEnv m) - => RepoLww - -> m (Maybe MyRefChan) -selectRepoFixmeRefChan r = do - let sql = [qc| - select refchan from ( - select lww - , refchan - , max(lwwseq) - from repoheadfixme - where lww = ? - group by lww, refchan - limit 1) - |] - - withState (select @(Only RefChanField) sql (Only r)) - <&> (fmap coerce . headMay) - -rpcSocketKey :: String -rpcSocketKey = - hashObject @HbSync (serialise "rpc-socket-name") & pretty & show - -rpcSocketFile :: MonadUnliftIO m => m FilePath -rpcSocketFile = do - dir <- liftIO $ getXdgDirectory XdgState hbs2_git_dashboard - pure $ dir rpcSocketKey - -setRPCSocket :: (DashBoardPerks m, MonadReader DashBoardEnv m) => FilePath -> m () -setRPCSocket soname = do - soFile <- rpcSocketFile - touch soFile - liftIO $ writeFile soFile soname - -delRPCSocket :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m () -delRPCSocket = do - rpcSocketFile >>= rm - -getRPCSocket :: (DashBoardPerks m, MonadReader DashBoardEnv m) => m (Maybe FilePath) -getRPCSocket = do - soFile <- rpcSocketFile - liftIO $ try @_ @IOError (readFile soFile) - <&> either (const Nothing) Just - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Commits.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Commits.hs deleted file mode 100644 index 91fc1cd7..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Commits.hs +++ /dev/null @@ -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 diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index.hs deleted file mode 100644 index 757c3236..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index.hs +++ /dev/null @@ -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 - - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Channels.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Channels.hs deleted file mode 100644 index 5766bcf9..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Channels.hs +++ /dev/null @@ -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)) - - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Peer.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Peer.hs deleted file mode 100644 index 65f4e5f1..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/State/Index/Peer.hs +++ /dev/null @@ -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) - - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Types.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Types.hs deleted file mode 100644 index a2e32f83..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/DashBoard/Types.hs +++ /dev/null @@ -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" diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Fixme.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Fixme.hs deleted file mode 100644 index 05734d12..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Fixme.hs +++ /dev/null @@ -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 - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Issue.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Issue.hs deleted file mode 100644 index 1c663966..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Issue.hs +++ /dev/null @@ -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 - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Markdown.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Markdown.hs deleted file mode 100644 index f3bf7e49..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Markdown.hs +++ /dev/null @@ -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 diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Blob.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Blob.hs deleted file mode 100644 index cd74ac3b..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Blob.hs +++ /dev/null @@ -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 - - diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Issues/Sidebar.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Issues/Sidebar.hs deleted file mode 100644 index ebb84ad1..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/Issues/Sidebar.hs +++ /dev/null @@ -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 () diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/TopInfoBlock.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/TopInfoBlock.hs deleted file mode 100644 index bc138a7e..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Parts/TopInfoBlock.hs +++ /dev/null @@ -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{..} diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Repo.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Repo.hs deleted file mode 100644 index 76984b68..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Repo.hs +++ /dev/null @@ -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 diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Root.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Root.hs deleted file mode 100644 index a77b346c..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Root.hs +++ /dev/null @@ -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" diff --git a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Types.hs b/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Types.hs deleted file mode 100644 index 21a59a52..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard-core/HBS2/Git/Web/Html/Types.hs +++ /dev/null @@ -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" diff --git a/hbs2-git-dashboard/hbs2-git-dashboard.cabal b/hbs2-git-dashboard/hbs2-git-dashboard.cabal deleted file mode 100644 index 5b069217..00000000 --- a/hbs2-git-dashboard/hbs2-git-dashboard.cabal +++ /dev/null @@ -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 - -