diff --git a/CHANGELOG.md b/CHANGELOG.md index 2668fd3ca..6c994dfbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [27.4.14](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.13...v27.4.14) (2023-07-14) + + +### Bug Fixes + +* **avs:** eliminate call to undefined in Esqueleto.Internals ([240c6f8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/240c6f81f81d1872317da01411fa67ec97e3b16d)) +* **job:** fix [#95](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/95) by implementing queued job deletion for admins ([5b9a554](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5b9a5545457dbe506d20f7362fb6e0d6bae4f7f4)) + +## [27.4.13](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.12...v27.4.13) (2023-07-12) + + +### Bug Fixes + +* **avs:** background avs synch yielding undefined due to wrong monad ([2e59d3c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2e59d3c2ea4d5017be9b4e578b7da12c4da0e2fa)) +* **lms:** add safeguard to LmsUserlist dispatch running twice, thus ending LMS prematurely ([a8df40d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a8df40d9f8943f2e0c4e219074486dbbf0eaf0fe)) +* **lpr:** fix [#96](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/96) by various minor improvements to PrintCenter ([80c632d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/80c632df1ca4871c10cdac1141d87f92a7646cf7)) +* **tutorial:** fix [#94](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/94) tutorial renaming (de) and template naming ([1ce8f75](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1ce8f75c2d192051929b1a74b17f4e6494961901)) + +## [27.4.12](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.11...v27.4.12) (2023-07-08) + + +### Bug Fixes + +* **avs:** attempt to fix avs background jobs ([bbaa42e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bbaa42eefaaae88982b091973adb295cdc0e80ff)) +* **avs:** avs background synchs and lms userlist result no longer block handler ([0beb0e4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0beb0e4011745ea51906e018c53548bb2f6d978e)) +* **avs:** fix [#7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/7) by sequencing avs background jobs one after another ([6dc3d8d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6dc3d8d059e132d19c119c5f1de906342fdf6d2c)) +* **notifications:** direct notifications now respect user triggers ([3e5f271](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3e5f271cacfcc5dbd95aa68a342f56db566f8dee)) + ## [27.4.11](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.10...v27.4.11) (2023-06-20) diff --git a/messages/uniworx/categories/courses/courses/de-de-formal.msg b/messages/uniworx/categories/courses/courses/de-de-formal.msg index f0df8c433..a0bf4391e 100644 --- a/messages/uniworx/categories/courses/courses/de-de-formal.msg +++ b/messages/uniworx/categories/courses/courses/de-de-formal.msg @@ -81,21 +81,21 @@ CourseSubmissionGroup: Feste Abgabegruppe SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer:innen aus den jeweiligen Abgabegruppen ersatzlos zu entfernen CourseParticipantsRegisterHeading: Kursartteilnehmer:innen hinzufügen CourseParticipantsRegisterActionAddParticipants: Personen zur Kursart anmelden -CourseParticipantsRegisterActionAddTutorialMembers: Personen zur Kursart und Kursgruppe anmelden +CourseParticipantsRegisterActionAddTutorialMembers: Personen zur Kursart und Kurs anmelden CourseParticipantsRegisterUsersField: Zur Kursart anzumeldende Personen CourseParticipantsRegisterUsersFieldTip: Bitte Ausweiskartennummer inklusive Punkt, Fraport Personalnummer oder Email angeben. Mehrere Personen bitte mit Komma oder Leerzeichen trennen. -CourseParticipantsRegisterTutorialOption: Kursartteilnehmer:innen zu Kursgruppe anmelden? -CourseParticipantsRegisterTutorialField: Kursgruppe -CourseParticipantsRegisterTutorialFieldTip: Ist aktuell keine Kursgruppe mit diesem Namen vorhanden, wird eine neue erstellt. Ist bereits eine Kursgruppe mit diesem Namen vorhanden, werden die Kursartteilnehmenden dieser hinzugefügt. +CourseParticipantsRegisterTutorialOption: Kursartteilnehmer:innen zu Kurs anmelden? +CourseParticipantsRegisterTutorialField: Kurs +CourseParticipantsRegisterTutorialFieldTip: Ist aktuell keine Kurs mit diesem Namen vorhanden, wird eine neue erstellt. Ist bereits eine Kurs mit diesem Namen vorhanden, werden die Kursartteilnehmenden dieser hinzugefügt. CourseParticipantsRegisterNoneGiven: Es wurden keine anzumeldenden Personen angegeben! CourseParticipantsRegisterNotFoundInAvs n@Int: Zu #{n} #{pluralDE n "Angabe konnte keine übereinstimmende Person" "Angaben konnten keine übereinstimmenden Personen"} im AVS gefunden werden CourseParticipantsRegisterTutorialFirstDayTip: Wenn ein neuer Kurs gemäß einer Vorlage erstellt wird, werden die Zeiten gemäß dem Starttag angepasst CourseParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt CourseParticipantsAlreadyRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits zur Kursart angemeldet -CourseParticipantsAlreadyTutorialMember n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits in dieser Kursgruppe angemeldet +CourseParticipantsAlreadyTutorialMember n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits in dieser Kurs angemeldet CourseParticipantsRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zur Kursart angemeldet -CourseParticipantsRegisteredTutorial n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zur Kursgruppe angemeldet +CourseParticipantsRegisteredTutorial n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zur Kurs angemeldet CourseParticipantsRegisterConfirmationHeading: Teilnehmer:innen hinzufügen CourseParticipantsRegisterUnnecessary: Alle angeforderten Anmeldungen sind bereits vorhanden. Es wurden keine Änderungen vorgenommen. CourseParticipantsRegisterConfirmInvalid: Ungültiges Bestätigungsformular! diff --git a/messages/uniworx/categories/health/de-de-formal.msg b/messages/uniworx/categories/health/de-de-formal.msg index 34566d000..2c8355493 100644 --- a/messages/uniworx/categories/health/de-de-formal.msg +++ b/messages/uniworx/categories/health/de-de-formal.msg @@ -3,12 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-or-later HealthReport: Instanz-Zustand -HealthMatchingClusterConfig: Cluster-geteilte Konfiguration ist aktuell -HealthHTTPReachable: Cluster kann an der erwarteten URL über HTTP erreicht werden -HealthLDAPAdmins: Anteil der Administrator:innen mit LDAP Authentifizierung, welche tatsächlich im LDAP-Verzeichnis gefunden werden können -HealthSMTPConnect: SMTP-Server kann erreicht werden -HealthWidgetMemcached: Memcached-Server liefert Widgets korrekt aus -HealthActiveJobExecutors: Anteil der job-workers, die neue Befehle annehmen +HealthCheckMatchingClusterConfig: Cluster-geteilte Konfiguration ist aktuell +HealthCheckHTTPReachable: Cluster kann an der erwarteten URL über HTTP erreicht werden +HealthCheckLDAPAdmins: Anteil der Administrator:innen mit LDAP Authentifizierung, welche tatsächlich im LDAP-Verzeichnis gefunden werden können +HealthCheckSMTPConnect: SMTP-Server kann erreicht werden +HealthCheckWidgetMemcached: Memcached-Server liefert Widgets korrekt aus +HealthCheckActiveJobExecutors: Anteil der job-workers, die neue Befehle annehmen +HealthCheckDoesFlush: Abgearbeitete Jobs werden aufgeräumt InstanceIdentification: Instanz-Identifikation InstanceId: Instanz-Nummer ClusterId: Cluster-Nummer \ No newline at end of file diff --git a/messages/uniworx/categories/health/en-eu.msg b/messages/uniworx/categories/health/en-eu.msg index 1bf279300..4e24bd8bb 100644 --- a/messages/uniworx/categories/health/en-eu.msg +++ b/messages/uniworx/categories/health/en-eu.msg @@ -3,12 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-or-later HealthReport: Health report -HealthMatchingClusterConfig: Cluster config matches -HealthHTTPReachable: Cluster can be reached under the expected URL via HTTP -HealthLDAPAdmins: Proportion of administrators with LDAP authentication that were actually found in the LDAP directory -HealthSMTPConnect: SMTP server is reachable -HealthWidgetMemcached: Memcached server is serving widgets correctly -HealthActiveJobExecutors: Proportion of job workers accepting new jobs +HealthCheckMatchingClusterConfig: Cluster config matches +HealthCheckHTTPReachable: Cluster can be reached under the expected URL via HTTP +HealthCheckLDAPAdmins: Proportion of administrators with LDAP authentication that were actually found in the LDAP directory +HealthCheckSMTPConnect: SMTP server is reachable +HealthCheckWidgetMemcached: Memcached server is serving widgets correctly +HealthCheckActiveJobExecutors: Proportion of job workers accepting new jobs +HealthCheckDoesFlush: Executed jobs are removed InstanceIdentification: Instance identification InstanceId: Instance id ClusterId: Cluster id diff --git a/messages/uniworx/categories/print/de-de-formal.msg b/messages/uniworx/categories/print/de-de-formal.msg index 1eb9eb034..32fe30556 100644 --- a/messages/uniworx/categories/print/de-de-formal.msg +++ b/messages/uniworx/categories/print/de-de-formal.msg @@ -4,6 +4,7 @@ PJActAcknowledge: Druck und Versand bestätigen PJActReprint: Erneut drucken über APC +PJActReprintIgnoreReroute: Drucken auch bei aktiver Mail-Umleitung erzwingen PrintJobName: Bezeichnung PrintJobFilename: Dateiname PrintJobId !ident-ok: Id diff --git a/messages/uniworx/categories/print/en-eu.msg b/messages/uniworx/categories/print/en-eu.msg index a1090de43..053fd1a7e 100644 --- a/messages/uniworx/categories/print/en-eu.msg +++ b/messages/uniworx/categories/print/en-eu.msg @@ -4,6 +4,7 @@ PJActAcknowledge: Acknowledge printing and mailing PJActReprint: Print again via APC +PJActReprintIgnoreReroute: Force printing to APC, even if mail-reroute-to option is active PrintJobName: Description PrintJobFilename: Filename PrintJobId: Id diff --git a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg index 3d083e6e7..9087f1ca0 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg @@ -66,6 +66,7 @@ BreadcrumbFaq !ident-ok: FAQ BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen BreadcrumbCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen BreadcrumbAdminCrontab !ident-ok: Crontab +BreadcrumbAdminJobs !ident-ok: Jobs BreadcrumbError: Fehler BreadcrumbUpload !ident-ok: Upload BreadcrumbUserAdd: Benutzer:in anlegen diff --git a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg index deadd76e7..5763051d1 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg @@ -66,6 +66,7 @@ BreadcrumbFaq: FAQ BreadcrumbSheetPersonalisedFiles: Download personalised sheet files BreadcrumbCourseSheetPersonalisedFiles: Download template for personalised sheet files BreadcrumbAdminCrontab: Crontab +BreadcrumbAdminJobs !ident-ok: Jobs BreadcrumbError: Error BreadcrumbUpload: Upload BreadcrumbUserAdd: Add user diff --git a/messages/uniworx/utils/navigation/menu/de-de-formal.msg b/messages/uniworx/utils/navigation/menu/de-de-formal.msg index a253ee93c..06916dd81 100644 --- a/messages/uniworx/utils/navigation/menu/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/menu/de-de-formal.msg @@ -107,6 +107,7 @@ MenuFaq !ident-ok: FAQ MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen MenuAdminCrontab !ident-ok: Crontab +MenuAdminJobs: Job Warteschlange MenuGlossary: Begriffsverzeichnis MenuVersion: Versionsgeschichte MenuCourseNewsNew: Neue Kursartnachricht diff --git a/messages/uniworx/utils/navigation/menu/en-eu.msg b/messages/uniworx/utils/navigation/menu/en-eu.msg index 798d1468a..0c8086373 100644 --- a/messages/uniworx/utils/navigation/menu/en-eu.msg +++ b/messages/uniworx/utils/navigation/menu/en-eu.msg @@ -108,6 +108,7 @@ MenuFaq: FAQ MenuSheetPersonalisedFiles: Download personalised sheet files MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files MenuAdminCrontab: Crontab +MenuAdminJobs: Job queue MenuGlossary: Glossary MenuVersion: Version history MenuCourseNewsNew: Add course type news diff --git a/messages/uniworx/utils/table_column/de-de-formal.msg b/messages/uniworx/utils/table_column/de-de-formal.msg index b9e575dda..16d43de61 100644 --- a/messages/uniworx/utils/table_column/de-de-formal.msg +++ b/messages/uniworx/utils/table_column/de-de-formal.msg @@ -78,3 +78,11 @@ TableCompany: Firma TableCompanies: Firmen TableCompanyNos: Firmennummern TableSupervisor: Ansprechpartner +TableCreationTime: Erstellungszeit +TableJob !ident-ok: Job +TableJobContent !ident-ok: Parameter +TableJobLockTime: Bearbeitung seit +TableJobLockInstance: Bearbeiter +TableJobCreationInstance: Ersteller +ActJobDelete: Job entfernen +TableJobActDeleteFeedback n@Int m@Int: #{n}/#{m} Jobs entfernt \ No newline at end of file diff --git a/messages/uniworx/utils/table_column/en-eu.msg b/messages/uniworx/utils/table_column/en-eu.msg index 5187d80dc..17fbfe79a 100644 --- a/messages/uniworx/utils/table_column/en-eu.msg +++ b/messages/uniworx/utils/table_column/en-eu.msg @@ -78,3 +78,11 @@ TableCompany: Company TableCompanies: Companies TableCompanyNos: Company numbers TableSupervisor: Supervisor +TableCreationTime: Creation +TableJob !ident-ok: Job +TableJobContent !ident-ok: Parameters +TableJobLockTime: Lock time +TableJobLockInstance: Worker +TableJobCreationInstance: Creator +ActJobDelete: Delete job +TableJobActDeleteFeedback n@Int m@Int: #{n}/#{m} queued jobs deleted \ No newline at end of file diff --git a/nix/docker/demo-version.json b/nix/docker/demo-version.json index afe4fccef..82f0bdba7 100644 --- a/nix/docker/demo-version.json +++ b/nix/docker/demo-version.json @@ -1,3 +1,3 @@ { - "version": "27.4.11" + "version": "27.4.14" } diff --git a/nix/docker/version.json b/nix/docker/version.json index afe4fccef..82f0bdba7 100644 --- a/nix/docker/version.json +++ b/nix/docker/version.json @@ -1,3 +1,3 @@ { - "version": "27.4.11" + "version": "27.4.14" } diff --git a/package-lock.json b/package-lock.json index c2dc210aa..e9fdffe9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "27.4.11", + "version": "27.4.14", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6eccdb0b4..9f2805cf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "27.4.11", + "version": "27.4.14", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index 1791191ce..cfe01b928 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 27.4.11 +version: 27.4.14 dependencies: - base - yesod diff --git a/routes b/routes index 1932a90ac..675f15ed4 100644 --- a/routes +++ b/routes @@ -66,6 +66,7 @@ /admin/errMsg AdminErrMsgR GET POST /admin/tokens AdminTokensR GET POST /admin/crontab AdminCrontabR GET +/admin/crontab/jobs AdminJobsR GET POST /admin/avs AdminAvsR GET POST /admin/avs/#CryptoUUIDUser AdminAvsUserR GET /admin/ldap AdminLdapR GET POST diff --git a/src/Application.hs b/src/Application.hs index 6592a8342..90d344bfd 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -550,8 +550,8 @@ warpSettings foundation = defaultSettings & Set.filter (is _Just . (foundation ^. _appHealthCheckInterval)) atomically $ do results <- readTVar $ foundation ^. _appHealthReport - guard $ activeChecks == Set.map (classifyHealthReport . snd) results - guard . (== Min HealthSuccess) $ foldMap (Min . healthReportStatus . snd) results + guard $ activeChecks `Set.isSubsetOf` Set.map (classifyHealthReport . snd) results + guard . (/= Min HealthFailure) $ foldMap (Min . healthReportStatus . snd) results notifyReady | otherwise -> notifyReady @@ -679,7 +679,7 @@ appMain = runResourceT $ do interval <- mInterval let lastSuccess = maybeMonoid mResults & Set.filter (\(_, rep) -> classifyHealthReport rep == hc) - & Set.filter (\(_, rep) -> healthReportStatus rep >= HealthSuccess) + & Set.filter (\(_, rep) -> healthReportStatus rep > HealthFailure) & Set.mapMonotonic (view _1) & Set.lookupMax diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index f900b2857..7b30a26a5 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -319,6 +319,7 @@ appLanguagesOpts = do langOptions = map mkOption $ toList appLanguages return $ mkOptionList langOptions +embedRenderMessage ''UniWorX ''HealthCheck id embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>) embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel embedRenderMessage ''UniWorX ''StudyFieldType id diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index cfefd462f..61889afd1 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -112,6 +112,7 @@ breadcrumb AdminTestPdfR = i18nCrumb MsgMenuAdminTest $ Just breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR breadcrumb AdminTokensR = i18nCrumb MsgMenuAdminTokens $ Just AdminR breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR +breadcrumb AdminJobsR = i18nCrumb MsgBreadcrumbAdminJobs $ Just AdminCrontabR breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR breadcrumb AdminAvsUserR{} = i18nCrumb MsgAvsPersonInfo $ Just AdminAvsR breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR @@ -2398,6 +2399,13 @@ pageActions PrintCenterR = do dayLinks <- mapM toDayAck $ Map.toAscList dayMap return $ manualSend : take 9 dayLinks +pageActions AdminCrontabR = return + [ NavPageActionPrimary + { navLink = defNavLink MsgMenuAdminJobs AdminJobsR + , navChildren = [] + } + ] + pageActions _ = return [] submissionList :: ( MonadIO m diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index 3775b0359..97cc51c45 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -54,9 +54,8 @@ getAdminProblemsR = do -- (Left (UnsupportedContentType "text/html" resp)) -> Left $ text2widget "Html received" (Left e) -> return $ Left $ text2widget $ tshow (e :: SomeException) (Right AvsLicenceDifferences{..}) -> do - let problemIds = avsLicenceDiffRevokeAll <> avsLicenceDiffGrantVorfeld <> avsLicenceDiffRevokeRollfeld <> avsLicenceDiffGrantRollfeld - -- mapM_ (queueJob' . flip JobSynchroniseAvsId cutOffAvsSynch) problemIds - runDBJobs . forM_ problemIds $ queueDBJob . flip JobSynchroniseAvsId (Just nowaday) + let problemIds = avsLicenceDiffRevokeAll <> avsLicenceDiffGrantVorfeld <> avsLicenceDiffRevokeRollfeld <> avsLicenceDiffGrantRollfeld + forM_ (take 42 $ Set.toList problemIds) $ queueJob' . flip JobSynchroniseAvsId (Just nowaday) return $ Right ( Set.size avsLicenceDiffRevokeAll , Set.size avsLicenceDiffGrantVorfeld diff --git a/src/Handler/Admin/Crontab.hs b/src/Handler/Admin/Crontab.hs index 12f4349de..5cb000074 100644 --- a/src/Handler/Admin/Crontab.hs +++ b/src/Handler/Admin/Crontab.hs @@ -6,23 +6,35 @@ module Handler.Admin.Crontab ( getAdminCrontabR + , getAdminJobsR + , postAdminJobsR ) where import Import import Jobs -import Handler.Utils.DateTime +import Handler.Utils -import qualified Data.Aeson.Encode.Pretty as Pretty -import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder') +-- 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 @@ -89,10 +101,125 @@ getAdminCrontabR = do provideJson mCrontab' provideRep . return . Text.Builder.toLazyText $ doEnc mCrontab' where - doEnc :: _ => a -> _ - doEnc = encodePrettyToTextBuilder' Pretty.defConfig + 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 + +-- Not yet needed, since there is no additional data for now (also, postprocess did not type somehow) +-- 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 JobTableAction) + acts = Map.singleton ActJobDelete $ pure ActJobDelete + 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 JobTableAction, DBFormResult QueuedJobId Bool (DBRow (Entity QueuedJob))) + -- -> FormResult (JobTableAction, 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 + (ActJobDelete, jobIds) -> do + let jobReq = length jobIds + rmvd <- fromIntegral <$> runDB (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 \ No newline at end of file diff --git a/src/Handler/Course/ParticipantInvite.hs b/src/Handler/Course/ParticipantInvite.hs index 4b79d8c86..d31cd0d41 100644 --- a/src/Handler/Course/ParticipantInvite.hs +++ b/src/Handler/Course/ParticipantInvite.hs @@ -52,7 +52,7 @@ tutorialDefaultName :: Maybe TutorialType -> Day -> TutorialName tutorialDefaultName Nothing = CI.mk . tshow -- Don't use user date display setting, so that tutorial default names conform to all users tutorialDefaultName (Just ttyp) = let prefix = CI.mk $ snd $ Text.breakOnEnd (CI.original tutorialTypeSeparator) $ CI.original ttyp - in ((prefix <> tutorialTypeSeparator) <>) . tutorialDefaultName Nothing + in (<> (tutorialTypeSeparator <> prefix)) . tutorialDefaultName Nothing data ButtonCourseRegisterMode = BtnCourseRegisterConfirm | BtnCourseRegisterAbort deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) diff --git a/src/Handler/Health.hs b/src/Handler/Health.hs index aad83d566..e06f688ae 100644 --- a/src/Handler/Health.hs +++ b/src/Handler/Health.hs @@ -6,7 +6,7 @@ module Handler.Health where import Import --- import Handler.Utils +import Handler.Utils.DateTime (formatTimeW) import qualified Data.Aeson.Encode.Pretty as Aeson import qualified Data.Text.Lazy.Builder as Builder @@ -34,7 +34,7 @@ getHealthR = do waitResult <- atomically $ maybe (pure $ Left False) (fmap (const $ Left True) . waitDelay) delay <|> (fmap Right . assertM (not. Set.null) $ readTVar reportStore) case waitResult of Left False -> sendResponseStatus noContent204 () - Left True -> sendResponseStatus internalServerError500 ("System is not generating HealthReports" :: Text) + Left True -> sendResponseStatus internalServerError500 ("System is not generating HealthReports" :: Text) -- can this ever happen after it was non-null? Right _ -> redirect HealthR Just healthReports -> do let (Max lastUpdated, Min status) = ofoldMap1 (Max *** Min . healthReportStatus) healthReports @@ -48,37 +48,41 @@ getHealthR = do setLastModified lastUpdated let status' - | HealthSuccess <- status - = ok200 - | otherwise + | HealthFailure <- status = internalServerError500 + | otherwise + = ok200 sendResponseStatus status' <=< selectRep $ do provideRep . siteLayoutMsg MsgHealthReport $ do setTitleI MsgHealthReport [whamlet| $newline never +
- Ausbilder:innen werden ad hoc pro Kursgruppe festgelegt.
+ Ausbilder:innen werden ad hoc pro Kurs festgelegt.
- Eine Kursgruppe kann beliebig viele Ausbilder:innen haben und ein Ausbilder kann beliebig viele Kursegruppen betreuen.
+ Eine Kurs kann beliebig viele Ausbilder:innen haben und ein Ausbilder kann beliebig viele Kursegruppen betreuen.
Ausbilder:innen haben Zugriff auf die Namen und Studiendaten ihrer Kursteilnehmer:innen, können Mitteilungen an sie verschicken (analog zu Kursartmitteilungen) und Teilnehmer:innen aus ihrem Kurs entfernen.
@@ -307,16 +307,16 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
Eine vorherige Anmeldung zur Kursart ist Voraussetzung.
- Die Anmeldung kann pro Kursgruppe zeitlich beschränkt werden. + Die Anmeldung kann pro Kurs zeitlich beschränkt werden.
- Kursgruppen können mit einer Registrierungs-Gruppe versehen werden.
+ Kurse können mit einer Registrierungs-Gruppe versehen werden.
Es handelt sich hierbei um einen beliebig wählbaren Text, der ansonsten keine Bedeutung hat.
Lernenden wird die Anmeldung nur in einem Kurs pro Registrierungs-Gruppe erlaubt.
Leere Registrierungs-Gruppen (d.h. es wurde keine Registrierungs-Gruppe angegeben) zählen hierbei als verschieden.
- Um die Anmeldung in beliebig viele Kursgruppen zuzulassen können alle Registrierungs-Gruppen leer gelassen werden. + Um die Anmeldung in beliebig viele Kurse zuzulassen können alle Registrierungs-Gruppen leer gelassen werden.
- ^{pjTable}
\ No newline at end of file
+ ^{pjTable}
+
+