-- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Winnie Ros -- -- SPDX-License-Identifier: AGPL-3.0-or-later {-# OPTIONS_GHC -fno-warn-orphans #-} module Handler.Admin.Crontab ( getAdminCrontabR , getAdminJobsR , postAdminJobsR ) where import Import import Jobs import Handler.Utils -- import Data.Aeson (fromJSON) -- import qualified Data.Aeson as Aeson -- import qualified Data.Aeson.Types as Aeson import qualified Data.Aeson.Encode.Pretty as Pretty -- import qualified Data.CaseInsensitive as CI import qualified Data.Text as Text import qualified Data.Text.Lazy.Builder as Text.Builder import qualified Data.Set as Set import qualified Data.Map as Map import qualified Data.HashSet as HashSet import qualified Data.HashMap.Strict as HashMap import qualified Data.UUID as UUID import Database.Persist.Sql (deleteWhereCount) import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Utils as E -- import Database.Esqueleto.Utils.TH deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece' 1 } ''CronNextMatch getAdminCrontabR :: Handler TypedContent getAdminCrontabR = do jState <- getsYesod appJobState mCrontab' <- atomically . runMaybeT $ do JobState{jobCurrentCrontab} <- MaybeT $ tryReadTMVar jState MaybeT $ readTVar jobCurrentCrontab let mCrontab = mCrontab' <&> _2 %~ filter (hasn't $ _3 . _MatchNone) instanceId <- getsYesod appInstanceID selectRep $ do provideRep $ do crontabBearer <- runMaybeT . hoist runDB $ do guardM $ hasGlobalGetParam GetGenerateToken uid <- MaybeT maybeAuthId guardM . lift . existsBy $ UniqueUserGroupMember UserGroupCrontab uid encodeBearer =<< bearerToken (HashSet.singleton . Left $ toJSON UserGroupCrontab) Nothing (HashMap.singleton BearerTokenRouteEval $ HashSet.singleton AdminCrontabR) Nothing (Just Nothing) Nothing siteLayoutMsg MsgHeadingAdminCrontab $ do setTitleI MsgHeadingAdminCrontab [whamlet| $newline never $maybe t <- crontabBearer
                #{toPathPiece t}
          
_{MsgAdminInstanceId}
#{UUID.toText instanceId}
$maybe (genTime, crontab) <- mCrontab

^{formatTimeW SelFormatDateTime genTime} $forall (job, lExec, match) <- crontab
$case match $of MatchAsap _{MsgCronMatchAsap} $of MatchNone _{MsgCronMatchNone} $of MatchAt t ^{formatTimeW SelFormatDateTime t} $maybe lT <- lExec ^{formatTimeW SelFormatDateTime lT} #{doEnc job} $nothing

_{MsgAdminCrontabNotGenerated} |] provideJson mCrontab' provideRep . return . Text.Builder.toLazyText $ doEnc mCrontab' where doEnc :: ToJSON a => a -> _ doEnc = Pretty.encodePrettyToTextBuilder' Pretty.defConfig { Pretty.confIndent = Pretty.Spaces 2 , Pretty.confCompare = comparing $ \t -> ( t `elem` ["instruction", "job", "notification"] , Text.splitOn "-" t ) } data JobTableAction = ActJobDelete deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) instance Universe JobTableAction instance Finite JobTableAction nullaryPathPiece ''JobTableAction $ camelToPathPiece' 1 embedRenderMessage ''UniWorX ''JobTableAction id data JobTableActionData = ActJobDeleteData deriving (Eq, Ord, Read, Show, Generic) getAdminJobsR, postAdminJobsR :: Handler Html getAdminJobsR = postAdminJobsR postAdminJobsR = do let jobsDBTable = DBTable{..} where resultJob :: Lens' (DBRow (Entity QueuedJob)) (Entity QueuedJob) resultJob = _dbrOutput dbtIdent :: Text dbtIdent = "queued-jobs" dbtSQLQuery = return dbtRowKey = (E.^. QueuedJobId) dbtProj = dbtProjId dbtColonnade = mconcat [ dbSelect (applying _2) id (return . view (resultJob . _entityKey)) , sortable (Just "job") (i18nCell MsgTableJob) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> cellMaybe textCell $ getJobName queuedJobContent , sortable (Just "creation-time") (i18nCell MsgTableCreationTime) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> dateTimeCell queuedJobCreationTime , sortable (Just "content") (i18nCell MsgTableJobContent) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> cell [whamlet|#{doEnc queuedJobContent}|] & addCellClass ("json"::Text) , sortable (Just "lock-time") (i18nCell MsgTableJobLockTime) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> cellMaybe dateTimeCell queuedJobLockTime , sortable (Just "lock-instance") (i18nCell MsgTableJobLockInstance) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> cellMaybe (stringCell . show) queuedJobLockInstance , sortable (Just "creation-instance") (i18nCell MsgTableJobCreationInstance) $ \(view $ resultJob . _entityVal -> QueuedJob{..}) -> stringCell $ show queuedJobCreationInstance ] dbtSorting = Map.fromList [ ("creation-time" , SortColumnNullsInv (E.^. QueuedJobCreationTime)) , ("job" , SortColumn (\v -> v E.^. QueuedJobContent E.->>. "job")) , ("content" , SortColumn (E.^. QueuedJobContent)) , ("lock-time" , SortColumnNullsInv (E.^. QueuedJobLockTime)) , ("lock-instance" , SortColumn (E.^. QueuedJobLockInstance)) , ("creation-instance", SortColumn (E.^. QueuedJobCreationInstance)) ] dbtFilter = Map.fromList [ ("job", FilterColumn $ E.mkContainsFilter (\v -> v E.^. QueuedJobContent E.->>. "job")) ] dbtFilterUI = \mPrev -> mconcat [ prismAForm (singletonFilter "job" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift textField) (fslI MsgTableJob) ] dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout } acts :: Map JobTableAction (AForm Handler JobTableActionData) acts = Map.singleton ActJobDelete $ pure ActJobDeleteData dbtParams = DBParamsForm { dbParamsFormAdditional = renderAForm FormStandard $ (, mempty) . First . Just <$> multiActionA acts (fslI MsgTableAction) Nothing , dbParamsFormMethod = POST , dbParamsFormAction = Nothing -- Just $ SomeRoute currentRoute , dbParamsFormAttrs = [] , dbParamsFormSubmit = FormSubmit , dbParamsFormEvaluate = liftHandler . runFormPost , dbParamsFormResult = id , dbParamsFormIdent = def } dbtCsvEncode = noCsvEncode dbtCsvDecode = Nothing dbtExtraReps = [] -- jobsDBTableValidator :: PSValidator (MForm Handler) (FormResult (First JobTableAction, DBFormResult QueuedJobId Bool (DBRow (Entity QueuedJob)))) jobsDBTableValidator = def & defaultSorting [SortDescBy "creation-time"] postprocess :: FormResult (First JobTableActionData, DBFormResult QueuedJobId Bool (DBRow (Entity QueuedJob))) -> FormResult (JobTableActionData, Set QueuedJobId) postprocess inp = do (First (Just act), jobMap) <- inp let jobSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) jobMap return (act, jobSet) (jobActRes, jobsTable) <- runDB (over _1 postprocess <$> dbTable jobsDBTableValidator jobsDBTable) formResult jobActRes $ \case (ActJobDeleteData, jobIds) -> do let jobReq = length jobIds rmvd <- runDB $ fromIntegral <$> deleteWhereCount [ QueuedJobLockTime ==. Nothing , QueuedJobLockInstance ==. Nothing , QueuedJobId <-. Set.toList jobIds ] addMessageI (bool Success Warning $ rmvd < jobReq) (MsgTableJobActDeleteFeedback rmvd jobReq) reloadKeepGetParams AdminJobsR siteLayoutMsg MsgMenuAdminJobs $ do setTitleI MsgMenuAdminJobs [whamlet| ^{jobsTable} |] where doEnc :: ToJSON a => a -> _ doEnc = Pretty.encodePrettyToTextBuilder' Pretty.defConfig { Pretty.confIndent = Pretty.Spaces 2 , Pretty.confCompare = comparing $ \t -> ( t `elem` ["job", "notification"] , Text.splitOn "-" t ) } getJobName :: Value -> Maybe Text getJobName (Object o) | Just (String s) <- HashMap.lookup "job" o = Just s -- $ kebabToCamel s getJobName _ = Nothing