Merge branch 'master' into fradrive/lms-type-refactor
This commit is contained in:
commit
9422892f72
28
CHANGELOG.md
28
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.
|
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)
|
## [27.4.11](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.10...v27.4.11) (2023-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -81,21 +81,21 @@ CourseSubmissionGroup: Feste Abgabegruppe
|
|||||||
SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer:innen aus den jeweiligen Abgabegruppen ersatzlos zu entfernen
|
SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer:innen aus den jeweiligen Abgabegruppen ersatzlos zu entfernen
|
||||||
CourseParticipantsRegisterHeading: Kursartteilnehmer:innen hinzufügen
|
CourseParticipantsRegisterHeading: Kursartteilnehmer:innen hinzufügen
|
||||||
CourseParticipantsRegisterActionAddParticipants: Personen zur Kursart anmelden
|
CourseParticipantsRegisterActionAddParticipants: Personen zur Kursart anmelden
|
||||||
CourseParticipantsRegisterActionAddTutorialMembers: Personen zur Kursart und Kursgruppe anmelden
|
CourseParticipantsRegisterActionAddTutorialMembers: Personen zur Kursart und Kurs anmelden
|
||||||
CourseParticipantsRegisterUsersField: Zur Kursart anzumeldende Personen
|
CourseParticipantsRegisterUsersField: Zur Kursart anzumeldende Personen
|
||||||
CourseParticipantsRegisterUsersFieldTip: Bitte Ausweiskartennummer inklusive Punkt, Fraport Personalnummer oder Email angeben. Mehrere Personen bitte mit Komma oder Leerzeichen trennen.
|
CourseParticipantsRegisterUsersFieldTip: Bitte Ausweiskartennummer inklusive Punkt, Fraport Personalnummer oder Email angeben. Mehrere Personen bitte mit Komma oder Leerzeichen trennen.
|
||||||
CourseParticipantsRegisterTutorialOption: Kursartteilnehmer:innen zu Kursgruppe anmelden?
|
CourseParticipantsRegisterTutorialOption: Kursartteilnehmer:innen zu Kurs anmelden?
|
||||||
CourseParticipantsRegisterTutorialField: Kursgruppe
|
CourseParticipantsRegisterTutorialField: Kurs
|
||||||
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.
|
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!
|
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
|
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
|
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
|
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
|
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
|
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
|
CourseParticipantsRegisterConfirmationHeading: Teilnehmer:innen hinzufügen
|
||||||
CourseParticipantsRegisterUnnecessary: Alle angeforderten Anmeldungen sind bereits vorhanden. Es wurden keine Änderungen vorgenommen.
|
CourseParticipantsRegisterUnnecessary: Alle angeforderten Anmeldungen sind bereits vorhanden. Es wurden keine Änderungen vorgenommen.
|
||||||
CourseParticipantsRegisterConfirmInvalid: Ungültiges Bestätigungsformular!
|
CourseParticipantsRegisterConfirmInvalid: Ungültiges Bestätigungsformular!
|
||||||
|
|||||||
@ -3,12 +3,13 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
HealthReport: Instanz-Zustand
|
HealthReport: Instanz-Zustand
|
||||||
HealthMatchingClusterConfig: Cluster-geteilte Konfiguration ist aktuell
|
HealthCheckMatchingClusterConfig: Cluster-geteilte Konfiguration ist aktuell
|
||||||
HealthHTTPReachable: Cluster kann an der erwarteten URL über HTTP erreicht werden
|
HealthCheckHTTPReachable: 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
|
HealthCheckLDAPAdmins: Anteil der Administrator:innen mit LDAP Authentifizierung, welche tatsächlich im LDAP-Verzeichnis gefunden werden können
|
||||||
HealthSMTPConnect: SMTP-Server kann erreicht werden
|
HealthCheckSMTPConnect: SMTP-Server kann erreicht werden
|
||||||
HealthWidgetMemcached: Memcached-Server liefert Widgets korrekt aus
|
HealthCheckWidgetMemcached: Memcached-Server liefert Widgets korrekt aus
|
||||||
HealthActiveJobExecutors: Anteil der job-workers, die neue Befehle annehmen
|
HealthCheckActiveJobExecutors: Anteil der job-workers, die neue Befehle annehmen
|
||||||
|
HealthCheckDoesFlush: Abgearbeitete Jobs werden aufgeräumt
|
||||||
InstanceIdentification: Instanz-Identifikation
|
InstanceIdentification: Instanz-Identifikation
|
||||||
InstanceId: Instanz-Nummer
|
InstanceId: Instanz-Nummer
|
||||||
ClusterId: Cluster-Nummer
|
ClusterId: Cluster-Nummer
|
||||||
@ -3,12 +3,13 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
HealthReport: Health report
|
HealthReport: Health report
|
||||||
HealthMatchingClusterConfig: Cluster config matches
|
HealthCheckMatchingClusterConfig: Cluster config matches
|
||||||
HealthHTTPReachable: Cluster can be reached under the expected URL via HTTP
|
HealthCheckHTTPReachable: 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
|
HealthCheckLDAPAdmins: Proportion of administrators with LDAP authentication that were actually found in the LDAP directory
|
||||||
HealthSMTPConnect: SMTP server is reachable
|
HealthCheckSMTPConnect: SMTP server is reachable
|
||||||
HealthWidgetMemcached: Memcached server is serving widgets correctly
|
HealthCheckWidgetMemcached: Memcached server is serving widgets correctly
|
||||||
HealthActiveJobExecutors: Proportion of job workers accepting new jobs
|
HealthCheckActiveJobExecutors: Proportion of job workers accepting new jobs
|
||||||
|
HealthCheckDoesFlush: Executed jobs are removed
|
||||||
InstanceIdentification: Instance identification
|
InstanceIdentification: Instance identification
|
||||||
InstanceId: Instance id
|
InstanceId: Instance id
|
||||||
ClusterId: Cluster id
|
ClusterId: Cluster id
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
PJActAcknowledge: Druck und Versand bestätigen
|
PJActAcknowledge: Druck und Versand bestätigen
|
||||||
PJActReprint: Erneut drucken über APC
|
PJActReprint: Erneut drucken über APC
|
||||||
|
PJActReprintIgnoreReroute: Drucken auch bei aktiver Mail-Umleitung erzwingen
|
||||||
PrintJobName: Bezeichnung
|
PrintJobName: Bezeichnung
|
||||||
PrintJobFilename: Dateiname
|
PrintJobFilename: Dateiname
|
||||||
PrintJobId !ident-ok: Id
|
PrintJobId !ident-ok: Id
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
PJActAcknowledge: Acknowledge printing and mailing
|
PJActAcknowledge: Acknowledge printing and mailing
|
||||||
PJActReprint: Print again via APC
|
PJActReprint: Print again via APC
|
||||||
|
PJActReprintIgnoreReroute: Force printing to APC, even if mail-reroute-to option is active
|
||||||
PrintJobName: Description
|
PrintJobName: Description
|
||||||
PrintJobFilename: Filename
|
PrintJobFilename: Filename
|
||||||
PrintJobId: Id
|
PrintJobId: Id
|
||||||
|
|||||||
@ -66,6 +66,7 @@ BreadcrumbFaq !ident-ok: FAQ
|
|||||||
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
|
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
|
||||||
BreadcrumbCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
|
BreadcrumbCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
|
||||||
BreadcrumbAdminCrontab !ident-ok: Crontab
|
BreadcrumbAdminCrontab !ident-ok: Crontab
|
||||||
|
BreadcrumbAdminJobs !ident-ok: Jobs
|
||||||
BreadcrumbError: Fehler
|
BreadcrumbError: Fehler
|
||||||
BreadcrumbUpload !ident-ok: Upload
|
BreadcrumbUpload !ident-ok: Upload
|
||||||
BreadcrumbUserAdd: Benutzer:in anlegen
|
BreadcrumbUserAdd: Benutzer:in anlegen
|
||||||
|
|||||||
@ -66,6 +66,7 @@ BreadcrumbFaq: FAQ
|
|||||||
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
|
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
|
||||||
BreadcrumbCourseSheetPersonalisedFiles: Download template for personalised sheet files
|
BreadcrumbCourseSheetPersonalisedFiles: Download template for personalised sheet files
|
||||||
BreadcrumbAdminCrontab: Crontab
|
BreadcrumbAdminCrontab: Crontab
|
||||||
|
BreadcrumbAdminJobs !ident-ok: Jobs
|
||||||
BreadcrumbError: Error
|
BreadcrumbError: Error
|
||||||
BreadcrumbUpload: Upload
|
BreadcrumbUpload: Upload
|
||||||
BreadcrumbUserAdd: Add user
|
BreadcrumbUserAdd: Add user
|
||||||
|
|||||||
@ -107,6 +107,7 @@ MenuFaq !ident-ok: FAQ
|
|||||||
MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen
|
MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen
|
||||||
MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
|
MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen
|
||||||
MenuAdminCrontab !ident-ok: Crontab
|
MenuAdminCrontab !ident-ok: Crontab
|
||||||
|
MenuAdminJobs: Job Warteschlange
|
||||||
MenuGlossary: Begriffsverzeichnis
|
MenuGlossary: Begriffsverzeichnis
|
||||||
MenuVersion: Versionsgeschichte
|
MenuVersion: Versionsgeschichte
|
||||||
MenuCourseNewsNew: Neue Kursartnachricht
|
MenuCourseNewsNew: Neue Kursartnachricht
|
||||||
|
|||||||
@ -108,6 +108,7 @@ MenuFaq: FAQ
|
|||||||
MenuSheetPersonalisedFiles: Download personalised sheet files
|
MenuSheetPersonalisedFiles: Download personalised sheet files
|
||||||
MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files
|
MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files
|
||||||
MenuAdminCrontab: Crontab
|
MenuAdminCrontab: Crontab
|
||||||
|
MenuAdminJobs: Job queue
|
||||||
MenuGlossary: Glossary
|
MenuGlossary: Glossary
|
||||||
MenuVersion: Version history
|
MenuVersion: Version history
|
||||||
MenuCourseNewsNew: Add course type news
|
MenuCourseNewsNew: Add course type news
|
||||||
|
|||||||
@ -78,3 +78,11 @@ TableCompany: Firma
|
|||||||
TableCompanies: Firmen
|
TableCompanies: Firmen
|
||||||
TableCompanyNos: Firmennummern
|
TableCompanyNos: Firmennummern
|
||||||
TableSupervisor: Ansprechpartner
|
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
|
||||||
@ -78,3 +78,11 @@ TableCompany: Company
|
|||||||
TableCompanies: Companies
|
TableCompanies: Companies
|
||||||
TableCompanyNos: Company numbers
|
TableCompanyNos: Company numbers
|
||||||
TableSupervisor: Supervisor
|
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
|
||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": "27.4.11"
|
"version": "27.4.14"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": "27.4.11"
|
"version": "27.4.14"
|
||||||
}
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "27.4.11",
|
"version": "27.4.14",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "27.4.11",
|
"version": "27.4.14",
|
||||||
"description": "",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: uniworx
|
name: uniworx
|
||||||
version: 27.4.11
|
version: 27.4.14
|
||||||
dependencies:
|
dependencies:
|
||||||
- base
|
- base
|
||||||
- yesod
|
- yesod
|
||||||
|
|||||||
1
routes
1
routes
@ -66,6 +66,7 @@
|
|||||||
/admin/errMsg AdminErrMsgR GET POST
|
/admin/errMsg AdminErrMsgR GET POST
|
||||||
/admin/tokens AdminTokensR GET POST
|
/admin/tokens AdminTokensR GET POST
|
||||||
/admin/crontab AdminCrontabR GET
|
/admin/crontab AdminCrontabR GET
|
||||||
|
/admin/crontab/jobs AdminJobsR GET POST
|
||||||
/admin/avs AdminAvsR GET POST
|
/admin/avs AdminAvsR GET POST
|
||||||
/admin/avs/#CryptoUUIDUser AdminAvsUserR GET
|
/admin/avs/#CryptoUUIDUser AdminAvsUserR GET
|
||||||
/admin/ldap AdminLdapR GET POST
|
/admin/ldap AdminLdapR GET POST
|
||||||
|
|||||||
@ -550,8 +550,8 @@ warpSettings foundation = defaultSettings
|
|||||||
& Set.filter (is _Just . (foundation ^. _appHealthCheckInterval))
|
& Set.filter (is _Just . (foundation ^. _appHealthCheckInterval))
|
||||||
atomically $ do
|
atomically $ do
|
||||||
results <- readTVar $ foundation ^. _appHealthReport
|
results <- readTVar $ foundation ^. _appHealthReport
|
||||||
guard $ activeChecks == Set.map (classifyHealthReport . snd) results
|
guard $ activeChecks `Set.isSubsetOf` Set.map (classifyHealthReport . snd) results
|
||||||
guard . (== Min HealthSuccess) $ foldMap (Min . healthReportStatus . snd) results
|
guard . (/= Min HealthFailure) $ foldMap (Min . healthReportStatus . snd) results
|
||||||
notifyReady
|
notifyReady
|
||||||
| otherwise
|
| otherwise
|
||||||
-> notifyReady
|
-> notifyReady
|
||||||
@ -679,7 +679,7 @@ appMain = runResourceT $ do
|
|||||||
interval <- mInterval
|
interval <- mInterval
|
||||||
let lastSuccess = maybeMonoid mResults
|
let lastSuccess = maybeMonoid mResults
|
||||||
& Set.filter (\(_, rep) -> classifyHealthReport rep == hc)
|
& Set.filter (\(_, rep) -> classifyHealthReport rep == hc)
|
||||||
& Set.filter (\(_, rep) -> healthReportStatus rep >= HealthSuccess)
|
& Set.filter (\(_, rep) -> healthReportStatus rep > HealthFailure)
|
||||||
& Set.mapMonotonic (view _1)
|
& Set.mapMonotonic (view _1)
|
||||||
& Set.lookupMax
|
& Set.lookupMax
|
||||||
|
|
||||||
|
|||||||
@ -319,6 +319,7 @@ appLanguagesOpts = do
|
|||||||
langOptions = map mkOption $ toList appLanguages
|
langOptions = map mkOption $ toList appLanguages
|
||||||
return $ mkOptionList langOptions
|
return $ mkOptionList langOptions
|
||||||
|
|
||||||
|
embedRenderMessage ''UniWorX ''HealthCheck id
|
||||||
embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
|
embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
|
||||||
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
||||||
embedRenderMessage ''UniWorX ''StudyFieldType id
|
embedRenderMessage ''UniWorX ''StudyFieldType id
|
||||||
|
|||||||
@ -112,6 +112,7 @@ breadcrumb AdminTestPdfR = i18nCrumb MsgMenuAdminTest $ Just
|
|||||||
breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR
|
breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR
|
||||||
breadcrumb AdminTokensR = i18nCrumb MsgMenuAdminTokens $ Just AdminR
|
breadcrumb AdminTokensR = i18nCrumb MsgMenuAdminTokens $ Just AdminR
|
||||||
breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR
|
breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR
|
||||||
|
breadcrumb AdminJobsR = i18nCrumb MsgBreadcrumbAdminJobs $ Just AdminCrontabR
|
||||||
breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR
|
breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR
|
||||||
breadcrumb AdminAvsUserR{} = i18nCrumb MsgAvsPersonInfo $ Just AdminAvsR
|
breadcrumb AdminAvsUserR{} = i18nCrumb MsgAvsPersonInfo $ Just AdminAvsR
|
||||||
breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR
|
breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR
|
||||||
@ -2398,6 +2399,13 @@ pageActions PrintCenterR = do
|
|||||||
dayLinks <- mapM toDayAck $ Map.toAscList dayMap
|
dayLinks <- mapM toDayAck $ Map.toAscList dayMap
|
||||||
return $ manualSend : take 9 dayLinks
|
return $ manualSend : take 9 dayLinks
|
||||||
|
|
||||||
|
pageActions AdminCrontabR = return
|
||||||
|
[ NavPageActionPrimary
|
||||||
|
{ navLink = defNavLink MsgMenuAdminJobs AdminJobsR
|
||||||
|
, navChildren = []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
pageActions _ = return []
|
pageActions _ = return []
|
||||||
|
|
||||||
submissionList :: ( MonadIO m
|
submissionList :: ( MonadIO m
|
||||||
|
|||||||
@ -55,8 +55,7 @@ getAdminProblemsR = do
|
|||||||
(Left e) -> return $ Left $ text2widget $ tshow (e :: SomeException)
|
(Left e) -> return $ Left $ text2widget $ tshow (e :: SomeException)
|
||||||
(Right AvsLicenceDifferences{..}) -> do
|
(Right AvsLicenceDifferences{..}) -> do
|
||||||
let problemIds = avsLicenceDiffRevokeAll <> avsLicenceDiffGrantVorfeld <> avsLicenceDiffRevokeRollfeld <> avsLicenceDiffGrantRollfeld
|
let problemIds = avsLicenceDiffRevokeAll <> avsLicenceDiffGrantVorfeld <> avsLicenceDiffRevokeRollfeld <> avsLicenceDiffGrantRollfeld
|
||||||
-- mapM_ (queueJob' . flip JobSynchroniseAvsId cutOffAvsSynch) problemIds
|
forM_ (take 42 $ Set.toList problemIds) $ queueJob' . flip JobSynchroniseAvsId (Just nowaday)
|
||||||
runDBJobs . forM_ problemIds $ queueDBJob . flip JobSynchroniseAvsId (Just nowaday)
|
|
||||||
return $ Right
|
return $ Right
|
||||||
( Set.size avsLicenceDiffRevokeAll
|
( Set.size avsLicenceDiffRevokeAll
|
||||||
, Set.size avsLicenceDiffGrantVorfeld
|
, Set.size avsLicenceDiffGrantVorfeld
|
||||||
|
|||||||
@ -6,23 +6,35 @@
|
|||||||
|
|
||||||
module Handler.Admin.Crontab
|
module Handler.Admin.Crontab
|
||||||
( getAdminCrontabR
|
( getAdminCrontabR
|
||||||
|
, getAdminJobsR
|
||||||
|
, postAdminJobsR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
import Jobs
|
import Jobs
|
||||||
import Handler.Utils.DateTime
|
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.Aeson.Encode.Pretty as Pretty
|
||||||
import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder')
|
|
||||||
|
|
||||||
|
-- import qualified Data.CaseInsensitive as CI
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import qualified Data.Text.Lazy.Builder as Text.Builder
|
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.HashSet as HashSet
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
import qualified Data.UUID as UUID
|
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
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = camelToPathPiece' 1
|
{ constructorTagModifier = camelToPathPiece' 1
|
||||||
@ -89,10 +101,125 @@ getAdminCrontabR = do
|
|||||||
provideJson mCrontab'
|
provideJson mCrontab'
|
||||||
provideRep . return . Text.Builder.toLazyText $ doEnc mCrontab'
|
provideRep . return . Text.Builder.toLazyText $ doEnc mCrontab'
|
||||||
where
|
where
|
||||||
doEnc :: _ => a -> _
|
doEnc :: ToJSON a => a -> _
|
||||||
doEnc = encodePrettyToTextBuilder' Pretty.defConfig
|
doEnc = Pretty.encodePrettyToTextBuilder' Pretty.defConfig
|
||||||
{ Pretty.confIndent = Pretty.Spaces 2
|
{ Pretty.confIndent = Pretty.Spaces 2
|
||||||
, Pretty.confCompare = comparing $ \t -> ( t `elem` ["instruction", "job", "notification"]
|
, Pretty.confCompare = comparing $ \t -> ( t `elem` ["instruction", "job", "notification"]
|
||||||
, Text.splitOn "-" t
|
, 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
|
||||||
@ -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 Nothing = CI.mk . tshow -- Don't use user date display setting, so that tutorial default names conform to all users
|
||||||
tutorialDefaultName (Just ttyp) =
|
tutorialDefaultName (Just ttyp) =
|
||||||
let prefix = CI.mk $ snd $ Text.breakOnEnd (CI.original tutorialTypeSeparator) $ CI.original 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
|
data ButtonCourseRegisterMode = BtnCourseRegisterConfirm | BtnCourseRegisterAbort
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ module Handler.Health where
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
-- import Handler.Utils
|
import Handler.Utils.DateTime (formatTimeW)
|
||||||
|
|
||||||
import qualified Data.Aeson.Encode.Pretty as Aeson
|
import qualified Data.Aeson.Encode.Pretty as Aeson
|
||||||
import qualified Data.Text.Lazy.Builder as Builder
|
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)
|
waitResult <- atomically $ maybe (pure $ Left False) (fmap (const $ Left True) . waitDelay) delay <|> (fmap Right . assertM (not. Set.null) $ readTVar reportStore)
|
||||||
case waitResult of
|
case waitResult of
|
||||||
Left False -> sendResponseStatus noContent204 ()
|
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
|
Right _ -> redirect HealthR
|
||||||
Just healthReports -> do
|
Just healthReports -> do
|
||||||
let (Max lastUpdated, Min status) = ofoldMap1 (Max *** Min . healthReportStatus) healthReports
|
let (Max lastUpdated, Min status) = ofoldMap1 (Max *** Min . healthReportStatus) healthReports
|
||||||
@ -48,37 +48,41 @@ getHealthR = do
|
|||||||
setLastModified lastUpdated
|
setLastModified lastUpdated
|
||||||
|
|
||||||
let status'
|
let status'
|
||||||
| HealthSuccess <- status
|
| HealthFailure <- status
|
||||||
= ok200
|
|
||||||
| otherwise
|
|
||||||
= internalServerError500
|
= internalServerError500
|
||||||
|
| otherwise
|
||||||
|
= ok200
|
||||||
sendResponseStatus status' <=< selectRep $ do
|
sendResponseStatus status' <=< selectRep $ do
|
||||||
provideRep . siteLayoutMsg MsgHealthReport $ do
|
provideRep . siteLayoutMsg MsgHealthReport $ do
|
||||||
setTitleI MsgHealthReport
|
setTitleI MsgHealthReport
|
||||||
[whamlet|
|
[whamlet|
|
||||||
$newline never
|
$newline never
|
||||||
|
<h2>
|
||||||
|
$case status
|
||||||
|
$of HealthSuccess
|
||||||
|
_{MsgMessageSuccess}
|
||||||
|
$of HealthInactive
|
||||||
|
_{MsgMessageWarning}
|
||||||
|
$of _
|
||||||
|
_{MsgMessageError}
|
||||||
|
<hr>
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
$forall (_, report) <- healthReports'
|
$forall (lUp, report) <- healthReports'
|
||||||
$case report
|
$case healthReportStatus report
|
||||||
$of HealthMatchingClusterConfig passed
|
$of HealthInactive
|
||||||
<dt .deflist__dt>_{MsgHealthMatchingClusterConfig}
|
$of hcstatus
|
||||||
<dd .deflist__dd>#{boolSymbol passed}
|
<dt .deflist__dt>
|
||||||
$of HealthHTTPReachable (Just passed)
|
_{classifyHealthReport report}
|
||||||
<dt .deflist__dt>_{MsgHealthHTTPReachable}
|
<dd .deflist__dd>
|
||||||
<dd .deflist__dd>#{boolSymbol passed}
|
#{boolSymbol (healthOk hcstatus)} #
|
||||||
$of HealthLDAPAdmins (Just found)
|
$case report
|
||||||
<dt .deflist__dt>_{MsgHealthLDAPAdmins}
|
$of HealthLDAPAdmins (Just found)
|
||||||
<dd .deflist__dd>#{textPercent found 1}
|
#{textPercent found 1}
|
||||||
$of HealthSMTPConnect (Just passed)
|
$of HealthActiveJobExecutors (Just active)
|
||||||
<dt .deflist__dt>_{MsgHealthSMTPConnect}
|
#{textPercent active 1}
|
||||||
<dd .deflist__dd>#{boolSymbol passed}
|
$of _
|
||||||
$of HealthWidgetMemcached (Just passed)
|
<div>
|
||||||
<dt .deflist__dt>_{MsgHealthWidgetMemcached}
|
^{formatTimeW SelFormatDateTime lUp}
|
||||||
<dd .deflist__dd>#{boolSymbol passed}
|
|
||||||
$of HealthActiveJobExecutors (Just active)
|
|
||||||
<dt .deflist__dt>_{MsgHealthActiveJobExecutors}
|
|
||||||
<dd .deflist__dd>#{textPercent active 1}
|
|
||||||
$of _
|
|
||||||
|]
|
|]
|
||||||
provideJson healthReports
|
provideJson healthReports
|
||||||
provideRep . return . Builder.toLazyText $ Aeson.encodePrettyToTextBuilder healthReports
|
provideRep . return . Builder.toLazyText $ Aeson.encodePrettyToTextBuilder healthReports
|
||||||
@ -105,7 +109,7 @@ getInstanceR = do
|
|||||||
provideRep . return $ tshow instanceInfo
|
provideRep . return $ tshow instanceInfo
|
||||||
|
|
||||||
|
|
||||||
-- Most simple page for simple liveness checks
|
-- Most simple page for simple liveness checks, but it always delivers 200
|
||||||
getStatusR :: Handler Html
|
getStatusR :: Handler Html
|
||||||
getStatusR = do
|
getStatusR = do
|
||||||
starttime <- getsYesod appStartTime
|
starttime <- getsYesod appStartTime
|
||||||
|
|||||||
@ -348,7 +348,6 @@ instance Finite LmsTableAction
|
|||||||
nullaryPathPiece ''LmsTableAction $ camelToPathPiece' 2
|
nullaryPathPiece ''LmsTableAction $ camelToPathPiece' 2
|
||||||
embedRenderMessage ''UniWorX ''LmsTableAction id
|
embedRenderMessage ''UniWorX ''LmsTableAction id
|
||||||
|
|
||||||
-- Not yet needed, since there is no additional data for now:
|
|
||||||
data LmsTableActionData = LmsActNotifyData
|
data LmsTableActionData = LmsActNotifyData
|
||||||
| LmsActRenewNotifyData
|
| LmsActRenewNotifyData
|
||||||
| LmsActRenewPinData -- no longer used
|
| LmsActRenewPinData -- no longer used
|
||||||
@ -692,14 +691,14 @@ postLmsR sid qsh = do
|
|||||||
|
|
||||||
fromIntegral <$> deleteWhereCount [LmsUserQualification ==. qid, LmsUserUser <-. usersList]
|
fromIntegral <$> deleteWhereCount [LmsUserQualification ==. qid, LmsUserUser <-. usersList]
|
||||||
|
|
||||||
runDBJobs $ forM_ selectedUsers $ \uid ->
|
forM_ selectedUsers $ \uid ->
|
||||||
queueDBJob $ JobLmsEnqueueUser { jQualification = qid, jUser = uid }
|
queueJob' $ JobLmsEnqueueUser { jQualification = qid, jUser = uid }
|
||||||
let mStatus = bool Success Warning $ delUsers < numUsers
|
let mStatus = bool Success Warning $ delUsers < numUsers
|
||||||
addMessageI mStatus $ MsgLmsActRestartFeedback delUsers numUsers
|
addMessageI mStatus $ MsgLmsActRestartFeedback delUsers numUsers
|
||||||
reloadKeepGetParams $ LmsR sid qsh
|
reloadKeepGetParams $ LmsR sid qsh
|
||||||
|
|
||||||
(action, selectedUsers) | isRenewPinAct action || isNotifyAct action -> do
|
(action, selectedUsers) | isRenewPinAct action || isNotifyAct action -> do
|
||||||
numExaminees <- runDBJobs $ do
|
numExaminees <- runDB $ do
|
||||||
okUsers <- selectList [ LmsUserQualification ==. qid -- matching qualification
|
okUsers <- selectList [ LmsUserQualification ==. qid -- matching qualification
|
||||||
, LmsUserEnded ==. Nothing -- not yet deleted
|
, LmsUserEnded ==. Nothing -- not yet deleted
|
||||||
, LmsUserStatus ==. Nothing -- not yet decided
|
, LmsUserStatus ==. Nothing -- not yet decided
|
||||||
@ -710,7 +709,7 @@ postLmsR sid qsh = do
|
|||||||
newPin <- liftIO randomLMSpw
|
newPin <- liftIO randomLMSpw
|
||||||
update lid [LmsUserPin =. newPin, LmsUserDatePin =. now, LmsUserResetPin =. True]
|
update lid [LmsUserPin =. newPin, LmsUserDatePin =. now, LmsUserResetPin =. True]
|
||||||
when (isNotifyAct action) $
|
when (isNotifyAct action) $
|
||||||
queueDBJob $ JobUserNotification { jRecipient = uid, jNotification = NotificationQualificationRenewal qid' False }
|
queueJob' $ JobUserNotification { jRecipient = uid, jNotification = NotificationQualificationRenewal qid' False }
|
||||||
return $ length okUsers
|
return $ length okUsers
|
||||||
let numSelected = length selectedUsers
|
let numSelected = length selectedUsers
|
||||||
diffSelected = numSelected - numExaminees
|
diffSelected = numSelected - numExaminees
|
||||||
|
|||||||
@ -212,7 +212,7 @@ postLmsResultR sid qsh = do
|
|||||||
|
|
||||||
-- Direct File Upload/Download
|
-- Direct File Upload/Download
|
||||||
|
|
||||||
saveResultCsv :: QualificationId -> Int -> LmsResultTableCsv -> JobDB Int
|
saveResultCsv :: QualificationId -> Int -> LmsResultTableCsv -> DB Int
|
||||||
saveResultCsv qid i LmsResultTableCsv{..} = do
|
saveResultCsv qid i LmsResultTableCsv{..} = do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
void $ upsert
|
void $ upsert
|
||||||
@ -238,12 +238,12 @@ postLmsResultUploadR sid qsh = do
|
|||||||
FormSuccess file -> do
|
FormSuccess file -> do
|
||||||
-- content <- fileSourceByteString file
|
-- content <- fileSourceByteString file
|
||||||
-- return $ Just (fileName file, content)
|
-- return $ Just (fileName file, content)
|
||||||
nr <- runDBJobs $ do
|
nr <- runDB $ do
|
||||||
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
||||||
nr <- runConduit $ fileSource file
|
nr <- runConduit $ fileSource file
|
||||||
.| decodeCsv
|
.| decodeCsv
|
||||||
.| foldMC (saveResultCsv qid) 0
|
.| foldMC (saveResultCsv qid) 0
|
||||||
queueDBJob $ JobLmsResults qid
|
queueJob' $ JobLmsResults qid
|
||||||
return nr
|
return nr
|
||||||
addMessage Success $ toHtml $ pack "Erfolgreicher Upload der Datei " <> fileName file <> pack (" mit " <> show nr <> " Zeilen")
|
addMessage Success $ toHtml $ pack "Erfolgreicher Upload der Datei " <> fileName file <> pack (" mit " <> show nr <> " Zeilen")
|
||||||
redirect $ LmsResultR sid qsh
|
redirect $ LmsResultR sid qsh
|
||||||
@ -267,7 +267,7 @@ postLmsResultDirectR sid qsh = do
|
|||||||
(status, msg) <- case files of
|
(status, msg) <- case files of
|
||||||
[(fhead,file)] -> do
|
[(fhead,file)] -> do
|
||||||
lmsDecoder <- getLmsCsvDecoder
|
lmsDecoder <- getLmsCsvDecoder
|
||||||
runDBJobs $ do
|
runDB $ do
|
||||||
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
||||||
enr <- try $ runConduit $ fileSource file
|
enr <- try $ runConduit $ fileSource file
|
||||||
.| lmsDecoder
|
.| lmsDecoder
|
||||||
@ -279,7 +279,7 @@ postLmsResultDirectR sid qsh = do
|
|||||||
Right nr -> do
|
Right nr -> do
|
||||||
let msg = "Success. LMS Result upload file " <> fileName file <> " containing " <> tshow nr <> " rows for " <> fhead <> ". "
|
let msg = "Success. LMS Result upload file " <> fileName file <> " containing " <> tshow nr <> " rows for " <> fhead <> ". "
|
||||||
$logInfoS "LMS" msg
|
$logInfoS "LMS" msg
|
||||||
when (nr > 0) $ queueDBJob $ JobLmsResults qid
|
when (nr > 0) $ queueJob' $ JobLmsResults qid
|
||||||
return (ok200, msg)
|
return (ok200, msg)
|
||||||
[] -> do
|
[] -> do
|
||||||
let msg = "Result upload file missing."
|
let msg = "Result upload file missing."
|
||||||
|
|||||||
@ -212,7 +212,7 @@ postLmsUserlistR sid qsh = do
|
|||||||
-- Direct File Upload/Download
|
-- Direct File Upload/Download
|
||||||
-- saveUserlistCsv :: (PersistUniqueWrite backend, MonadIO m, BaseBackend backend ~ SqlBackend, Enum b) =>
|
-- saveUserlistCsv :: (PersistUniqueWrite backend, MonadIO m, BaseBackend backend ~ SqlBackend, Enum b) =>
|
||||||
-- Key Qualification -> b -> LmsUserlistTableCsv -> ReaderT backend m b
|
-- Key Qualification -> b -> LmsUserlistTableCsv -> ReaderT backend m b
|
||||||
saveUserlistCsv :: QualificationId -> Int -> LmsUserlistTableCsv -> JobDB Int
|
saveUserlistCsv :: QualificationId -> Int -> LmsUserlistTableCsv -> DB Int
|
||||||
saveUserlistCsv qid i LmsUserlistTableCsv{..} = do
|
saveUserlistCsv qid i LmsUserlistTableCsv{..} = do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
void $ upsert
|
void $ upsert
|
||||||
@ -236,10 +236,10 @@ postLmsUserlistUploadR sid qsh = do
|
|||||||
((result,widget), enctype) <- runFormPost makeUserlistUploadForm
|
((result,widget), enctype) <- runFormPost makeUserlistUploadForm
|
||||||
case result of
|
case result of
|
||||||
FormSuccess file -> do
|
FormSuccess file -> do
|
||||||
nr <- runDBJobs $ do
|
nr <- runDB $ do
|
||||||
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
||||||
nr <- runConduit $ fileSource file .| decodeCsv .| foldMC (saveUserlistCsv qid) 0
|
nr <- runConduit $ fileSource file .| decodeCsv .| foldMC (saveUserlistCsv qid) 0
|
||||||
queueDBJob $ JobLmsUserlist qid
|
queueJob' $ JobLmsUserlist qid
|
||||||
return nr
|
return nr
|
||||||
addMessage Success $ toHtml $ pack "Erfolgreicher Upload der Datei " <> fileName file <> pack (" mit " <> show nr <> " Zeilen")
|
addMessage Success $ toHtml $ pack "Erfolgreicher Upload der Datei " <> fileName file <> pack (" mit " <> show nr <> " Zeilen")
|
||||||
redirect $ LmsUserlistR sid qsh
|
redirect $ LmsUserlistR sid qsh
|
||||||
@ -263,7 +263,7 @@ postLmsUserlistDirectR sid qsh = do
|
|||||||
(status, msg) <- case files of
|
(status, msg) <- case files of
|
||||||
[(fhead,file)] -> do
|
[(fhead,file)] -> do
|
||||||
lmsDecoder <- getLmsCsvDecoder
|
lmsDecoder <- getLmsCsvDecoder
|
||||||
runDBJobs $ do
|
runDB $ do
|
||||||
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
qid <- getKeyBy404 $ SchoolQualificationShort sid qsh
|
||||||
enr <- try $ runConduit $ fileSource file
|
enr <- try $ runConduit $ fileSource file
|
||||||
.| lmsDecoder
|
.| lmsDecoder
|
||||||
@ -275,7 +275,7 @@ postLmsUserlistDirectR sid qsh = do
|
|||||||
Right nr -> do
|
Right nr -> do
|
||||||
let msg = "Success. LMS Userlist upload file " <> fileName file <> " containing " <> tshow nr <> " rows for " <> fhead <> ". "
|
let msg = "Success. LMS Userlist upload file " <> fileName file <> " containing " <> tshow nr <> " rows for " <> fhead <> ". "
|
||||||
$logInfoS "LMS" msg
|
$logInfoS "LMS" msg
|
||||||
when (nr > 0) $ queueDBJob $ JobLmsUserlist qid
|
when (nr > 0) $ queueJob' $ JobLmsUserlist qid
|
||||||
return (ok200, msg)
|
return (ok200, msg)
|
||||||
[] -> do
|
[] -> do
|
||||||
let msg = "Userlist upload file missing."
|
let msg = "Userlist upload file missing."
|
||||||
|
|||||||
@ -127,7 +127,7 @@ nullaryPathPiece ''PJTableAction $ camelToPathPiece' 2
|
|||||||
embedRenderMessage ''UniWorX ''PJTableAction id
|
embedRenderMessage ''UniWorX ''PJTableAction id
|
||||||
|
|
||||||
-- Not yet needed, since there is no additional data for now:
|
-- Not yet needed, since there is no additional data for now:
|
||||||
data PJTableActionData = PJActAcknowledgeData | PJActReprintData
|
data PJTableActionData = PJActAcknowledgeData | PJActReprintData { ignoreReroute :: Maybe Bool }
|
||||||
deriving (Eq, Ord, Read, Show, Generic)
|
deriving (Eq, Ord, Read, Show, Generic)
|
||||||
|
|
||||||
type PJTableExpr = ( E.SqlExpr (Entity PrintJob)
|
type PJTableExpr = ( E.SqlExpr (Entity PrintJob)
|
||||||
@ -192,7 +192,7 @@ mkPJTable = do
|
|||||||
dbtRowKey = queryPrintJob >>> (E.^. PrintJobId)
|
dbtRowKey = queryPrintJob >>> (E.^. PrintJobId)
|
||||||
dbtProj = dbtProjId
|
dbtProj = dbtProjId
|
||||||
dbtColonnade = mconcat
|
dbtColonnade = mconcat
|
||||||
[ dbSelectIf (applying _2) id (return . view (resultPrintJob . _entityKey)) (\r -> isNothing $ r ^. resultPrintJob . _entityVal . _printJobAcknowledged)
|
[ dbSelect (applying _2) id (return . view (resultPrintJob . _entityKey)) -- condition for dbSelectIf: (\r -> isNothing $ r ^. resultPrintJob . _entityVal . _printJobAcknowledged)
|
||||||
, sortable (Just "created") (i18nCell MsgPrintJobCreated) $ \( view $ resultPrintJob . _entityVal . _printJobCreated -> t) -> dateTimeCell t
|
, sortable (Just "created") (i18nCell MsgPrintJobCreated) $ \( view $ resultPrintJob . _entityVal . _printJobCreated -> t) -> dateTimeCell t
|
||||||
, sortable (Just "acknowledged") (i18nCell MsgPrintJobAcknowledged) $ \( view $ resultPrintJob . _entityVal . _printJobAcknowledged -> t) -> maybeDateTimeCell t
|
, sortable (Just "acknowledged") (i18nCell MsgPrintJobAcknowledged) $ \( view $ resultPrintJob . _entityVal . _printJobAcknowledged -> t) -> maybeDateTimeCell t
|
||||||
, sortable (Just "filename") (i18nCell MsgPrintPDF) $ \r -> let k = r ^. resultPrintJob . _entityKey
|
, sortable (Just "filename") (i18nCell MsgPrintPDF) $ \r -> let k = r ^. resultPrintJob . _entityKey
|
||||||
@ -262,7 +262,8 @@ mkPJTable = do
|
|||||||
= let acts :: Map PJTableAction (AForm Handler PJTableActionData)
|
= let acts :: Map PJTableAction (AForm Handler PJTableActionData)
|
||||||
acts = mconcat
|
acts = mconcat
|
||||||
[ singletonMap PJActAcknowledge $ pure PJActAcknowledgeData
|
[ singletonMap PJActAcknowledge $ pure PJActAcknowledgeData
|
||||||
, singletonMap PJActReprint $ pure PJActReprintData
|
, singletonMap PJActReprint $ PJActReprintData
|
||||||
|
<$> aopt checkBoxField (fslI MsgPJActReprintIgnoreReroute) Nothing
|
||||||
]
|
]
|
||||||
in renderAForm FormStandard
|
in renderAForm FormStandard
|
||||||
$ (, mempty) . First . Just
|
$ (, mempty) . First . Just
|
||||||
@ -292,15 +293,23 @@ postPrintCenterR = do
|
|||||||
num <- runDB $ updateWhereCount [PrintJobAcknowledged ==. Nothing, PrintJobId <-. pjIds] [PrintJobAcknowledged =. Just now]
|
num <- runDB $ updateWhereCount [PrintJobAcknowledged ==. Nothing, PrintJobId <-. pjIds] [PrintJobAcknowledged =. Just now]
|
||||||
addMessageI Success $ MsgPrintJobAcknowledge num
|
addMessageI Success $ MsgPrintJobAcknowledge num
|
||||||
reloadKeepGetParams PrintCenterR
|
reloadKeepGetParams PrintCenterR
|
||||||
(PJActReprintData, Set.toList -> pjIds) -> do
|
(PJActReprintData{ignoreReroute}, Set.toList -> pjIds) -> do
|
||||||
let countOk = either (const $ Sum 0) (const $ Sum 1)
|
let countOk = either (const $ Sum 0) (const $ Sum 1)
|
||||||
oks <- runDB $ forM pjIds $ fmap countOk . reprintPDF
|
oks <- runDB $ forM pjIds $ fmap countOk . reprintPDF (fromMaybe False ignoreReroute)
|
||||||
let nr_oks = getSum $ mconcat oks
|
let nr_oks = getSum $ mconcat oks
|
||||||
nr_tot = length pjIds
|
nr_tot = length pjIds
|
||||||
mstat = bool Warning Success $ nr_oks == nr_tot
|
mstat = bool Warning Success $ nr_oks == nr_tot
|
||||||
addMessageI mstat $ MsgPrintJobReprint nr_oks nr_tot
|
addMessageI mstat $ MsgPrintJobReprint nr_oks nr_tot
|
||||||
reloadKeepGetParams PrintCenterR
|
reloadKeepGetParams PrintCenterR
|
||||||
|
siteConf <- getYesod
|
||||||
|
let lprConf = siteConf ^. _appLprConf
|
||||||
|
reroute = siteConf ^. _appMailRerouteTo
|
||||||
|
lprWgt = [whamlet|
|
||||||
|
LPR Konfiguration ist #{lprQueue lprConf}@#{lprHost lprConf}:#{lprPort lprConf}
|
||||||
|
<div>
|
||||||
|
$maybe _ <- reroute
|
||||||
|
Mail-reroute-to ist gesetzt, somit werden alle lpr Kommandos unterdrückt!
|
||||||
|
|]
|
||||||
siteLayoutMsg MsgMenuApc $ do
|
siteLayoutMsg MsgMenuApc $ do
|
||||||
setTitleI MsgMenuApc
|
setTitleI MsgMenuApc
|
||||||
$(widgetFile "print-center") -- i18nWidgetFile? Currently no text contained; displays just the table only
|
$(widgetFile "print-center") -- i18nWidgetFile? Currently no text contained; displays just the table only
|
||||||
|
|||||||
@ -359,11 +359,11 @@ postUsersR = do
|
|||||||
| Set.null usersSet && isNotSetSupervisor act ->
|
| Set.null usersSet && isNotSetSupervisor act ->
|
||||||
addMessageI Info MsgActionNoUsersSelected
|
addMessageI Info MsgActionNoUsersSelected
|
||||||
(UserLdapSyncData, userSet) -> do
|
(UserLdapSyncData, userSet) -> do
|
||||||
runDBJobs . forM_ userSet $ \uid -> queueDBJob $ JobSynchroniseLdapUser uid
|
forM_ userSet $ \uid -> queueJob' $ JobSynchroniseLdapUser uid
|
||||||
addMessageI Success . MsgSynchroniseLdapUserQueued $ Set.size userSet
|
addMessageI Success . MsgSynchroniseLdapUserQueued $ Set.size userSet
|
||||||
redirectKeepGetParams UsersR
|
redirectKeepGetParams UsersR
|
||||||
(UserAvsSyncData, userSet) -> do
|
(UserAvsSyncData, userSet) -> do
|
||||||
runDBJobs . forM_ userSet $ \uid -> queueDBJob $ JobSynchroniseAvsUser uid Nothing
|
forM_ userSet $ \uid -> queueJob' $ JobSynchroniseAvsUser uid Nothing
|
||||||
addMessageI Success . MsgSynchroniseAvsUserQueued $ Set.size userSet
|
addMessageI Success . MsgSynchroniseAvsUserQueued $ Set.size userSet
|
||||||
redirectKeepGetParams UsersR
|
redirectKeepGetParams UsersR
|
||||||
(UserHijack, Set.minView -> Just (uid, _)) ->
|
(UserHijack, Set.minView -> Just (uid, _)) ->
|
||||||
|
|||||||
@ -593,7 +593,7 @@ handleJobs' wNum = C.mapM_ $ \jctl -> hoist delimitInternalState . withJobWorker
|
|||||||
newReport@(healthReportStatus -> newStatus) <- lift $ generateHealthReport kind
|
newReport@(healthReportStatus -> newStatus) <- lift $ generateHealthReport kind
|
||||||
|
|
||||||
$logInfoS logIdent [st|#{tshow kind}: #{toPathPiece newStatus}|]
|
$logInfoS logIdent [st|#{tshow kind}: #{toPathPiece newStatus}|]
|
||||||
unless (newStatus == HealthSuccess) $ do
|
unless (newStatus > HealthFailure) $ do
|
||||||
$logErrorS logIdent [st|#{tshow kind}: #{tshow newReport}|]
|
$logErrorS logIdent [st|#{tshow kind}: #{tshow newReport}|]
|
||||||
|
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
|
|||||||
@ -298,7 +298,7 @@ dispatchJobLmsUserlist :: QualificationId -> JobHandler UniWorX
|
|||||||
dispatchJobLmsUserlist qid = JobHandlerAtomic act
|
dispatchJobLmsUserlist qid = JobHandlerAtomic act
|
||||||
where
|
where
|
||||||
act :: YesodJobDB UniWorX ()
|
act :: YesodJobDB UniWorX ()
|
||||||
act = do
|
act = whenM (exists [LmsUserlistQualification ==. qid]) $ do -- safeguard against multiple calls, which would close all learners due to first case below
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
-- result :: [(Entity LmsUser, Entity LmsUserlist)]
|
-- result :: [(Entity LmsUser, Entity LmsUserlist)]
|
||||||
results <- E.select $ do
|
results <- E.select $ do
|
||||||
|
|||||||
@ -12,9 +12,9 @@ module Jobs.Handler.SynchroniseAvs
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
import qualified Database.Esqueleto.Legacy as E hiding (upsert)
|
-- import qualified Database.Esqueleto.Legacy as E hiding (upsert)
|
||||||
import qualified Database.Esqueleto.PostgreSQL as E
|
-- import qualified Database.Esqueleto.PostgreSQL as E
|
||||||
import qualified Database.Esqueleto.Utils as E
|
-- import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
import qualified Data.Conduit.List as C
|
import qualified Data.Conduit.List as C
|
||||||
import Jobs.Queue
|
import Jobs.Queue
|
||||||
@ -23,23 +23,23 @@ import Handler.Utils.Avs
|
|||||||
|
|
||||||
dispatchJobSynchroniseAvs :: Natural -> Natural -> Natural -> Maybe Day -> JobHandler UniWorX
|
dispatchJobSynchroniseAvs :: Natural -> Natural -> Natural -> Maybe Day -> JobHandler UniWorX
|
||||||
dispatchJobSynchroniseAvs numIterations epoch iteration pause
|
dispatchJobSynchroniseAvs numIterations epoch iteration pause
|
||||||
-- TODO: refactor so that the AvsIdLookup becomes obsolete
|
= JobHandlerException . runDB $ do
|
||||||
= JobHandlerAtomic . runConduit $
|
now <- liftIO getCurrentTime
|
||||||
readUsers .| filterIteration .| sinkDBJobs
|
todos <- runConduit $ readUsers .| filterIteration now .| sinkList
|
||||||
|
putMany todos
|
||||||
where
|
where
|
||||||
readUsers :: ConduitT () UserId (YesodJobDB UniWorX) ()
|
readUsers :: ConduitT () UserId _ ()
|
||||||
readUsers = selectKeys [] []
|
readUsers = selectKeys [] []
|
||||||
|
|
||||||
filterIteration :: ConduitT UserId Job (YesodJobDB UniWorX) ()
|
filterIteration :: UTCTime -> ConduitT UserId AvsSync _ ()
|
||||||
filterIteration = C.mapMaybeM $ \userId -> runMaybeT $ do
|
filterIteration now = C.mapMaybeM $ \userId -> runMaybeT $ do
|
||||||
let
|
let
|
||||||
userIteration, currentIteration :: Integer
|
userIteration, currentIteration :: Integer
|
||||||
userIteration = toInteger (hash epoch `hashWithSalt` userId) `mod` toInteger numIterations
|
userIteration = toInteger (hash epoch `hashWithSalt` userId) `mod` toInteger numIterations
|
||||||
currentIteration = toInteger iteration `mod` toInteger numIterations
|
currentIteration = toInteger iteration `mod` toInteger numIterations
|
||||||
-- $logDebugS "SynchronisAvs" [st|User ##{tshow (fromSqlKey userId)}: sync on #{tshow userIteration}/#{tshow numIterations}, now #{tshow currentIteration}|]
|
-- $logDebugS "SynchronisAvs" [st|User ##{tshow (fromSqlKey userId)}: sync on #{tshow userIteration}/#{tshow numIterations}, now #{tshow currentIteration}|]
|
||||||
guard $ userIteration == currentIteration
|
guard $ userIteration == currentIteration
|
||||||
|
return $ AvsSync userId now pause
|
||||||
return $ JobSynchroniseAvsUser userId pause
|
|
||||||
|
|
||||||
-- dispatchJobSynchroniseAvs' :: Natural -> Natural -> Natural -> Maybe Day -> JobHandler UniWorX
|
-- dispatchJobSynchroniseAvs' :: Natural -> Natural -> Natural -> Maybe Day -> JobHandler UniWorX
|
||||||
-- dispatchJobSynchroniseAvs' numIterations epoch iteration pause = JobHandlerAtomic $ do
|
-- dispatchJobSynchroniseAvs' numIterations epoch iteration pause = JobHandlerAtomic $ do
|
||||||
@ -47,32 +47,34 @@ dispatchJobSynchroniseAvs numIterations epoch iteration pause
|
|||||||
|
|
||||||
dispatchJobSynchroniseAvsId :: AvsPersonId -> Maybe Day -> JobHandler UniWorX
|
dispatchJobSynchroniseAvsId :: AvsPersonId -> Maybe Day -> JobHandler UniWorX
|
||||||
dispatchJobSynchroniseAvsId apid pause = JobHandlerException $ do
|
dispatchJobSynchroniseAvsId apid pause = JobHandlerException $ do
|
||||||
ok <- runDBJobs $
|
ok <- runDB $ getBy (UniqueUserAvsId apid) >>=
|
||||||
getBy (UniqueUserAvsId apid) >>= \case
|
\case
|
||||||
(Just Entity{entityVal=UserAvs{userAvsUser=uid}}) -> do -- known user
|
(Just Entity{entityVal=UserAvs{userAvsUser=uid}}) -> do -- known user
|
||||||
workJobSychronizeAvs uid pause
|
workJobSychronizeAvs uid pause
|
||||||
return True
|
return True
|
||||||
Nothing -> -- unknown avsPersonId, attempt to create user
|
_ -> -- unknown avsPersonId, attempt to create user
|
||||||
return False
|
return False
|
||||||
-- flip (maybeM $ return False) (getBy $ UniqueUserAvsId apid) $ \Entity{entityVal=UserAvs{userAvsUser=uid}} -> do -- known user
|
|
||||||
-- workJobSychronizeAvs uid pause
|
|
||||||
-- return True
|
|
||||||
unless ok $ void $ maybeCatchAll $ upsertAvsUserById apid
|
unless ok $ void $ maybeCatchAll $ upsertAvsUserById apid
|
||||||
|
|
||||||
|
|
||||||
dispatchJobSynchroniseAvsUser :: UserId -> Maybe Day -> JobHandler UniWorX
|
dispatchJobSynchroniseAvsUser :: UserId -> Maybe Day -> JobHandler UniWorX
|
||||||
dispatchJobSynchroniseAvsUser uid pause = JobHandlerException $ runDBJobs $ workJobSychronizeAvs uid pause
|
dispatchJobSynchroniseAvsUser uid pause = JobHandlerException $ runDB $ workJobSychronizeAvs uid pause
|
||||||
|
|
||||||
workJobSychronizeAvs :: UserId -> Maybe Day -> JobDB ()
|
workJobSychronizeAvs :: UserId -> Maybe Day -> DB ()
|
||||||
workJobSychronizeAvs uid pause = do
|
workJobSychronizeAvs uid pause = do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
void $ E.upsert
|
-- void $ E.upsert
|
||||||
AvsSync { avsSyncUser = uid
|
-- AvsSync { avsSyncUser = uid
|
||||||
, avsSyncCreationTime = now
|
-- , avsSyncCreationTime = now
|
||||||
, avsSyncPause = pause
|
-- , avsSyncPause = pause
|
||||||
}
|
-- }
|
||||||
[ \oldSync -> (AvsSyncPause E.=. E.greatest (E.val pause) (oldSync E.^. AvsSyncPause)) oldSync ]
|
-- [ \oldSync -> (AvsSyncPause E.=. E.greatest (E.val pause) (oldSync E.^. AvsSyncPause)) oldSync ] -- causes Esqueleto to call undefined at Database.Esqueleto.Internal.Internal.renderUpdates:1308
|
||||||
queueDBJob JobSynchroniseAvsQueue
|
maybeM
|
||||||
|
(insert_ AvsSync{avsSyncUser=uid, avsSyncCreationTime=now, avsSyncPause=pause})
|
||||||
|
(\Entity{entityKey=asid, entityVal=AvsSync{avsSyncPause=oldPause}} ->
|
||||||
|
update asid [AvsSyncPause =. max pause oldPause, AvsSyncCreationTime =. now])
|
||||||
|
(getBy $ UniqueAvsSyncUser uid)
|
||||||
|
queueJob' JobSynchroniseAvsQueue
|
||||||
|
|
||||||
dispatchJobSynchroniseAvsQueue :: JobHandler UniWorX
|
dispatchJobSynchroniseAvsQueue :: JobHandler UniWorX
|
||||||
dispatchJobSynchroniseAvsQueue = JobHandlerException $ do
|
dispatchJobSynchroniseAvsQueue = JobHandlerException $ do
|
||||||
|
|||||||
@ -152,7 +152,7 @@ sinkDBJobs :: ConduitT Job Void (YesodJobDB UniWorX) ()
|
|||||||
sinkDBJobs = C.mapM_ queueDBJob
|
sinkDBJobs = C.mapM_ queueDBJob
|
||||||
|
|
||||||
runDBJobs :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => YesodJobDB UniWorX a -> m a
|
runDBJobs :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => YesodJobDB UniWorX a -> m a
|
||||||
-- | Replacement for/Wrapper around `runDB` when jobs need to be queued as part of a database transaction
|
-- | Blocking! Replacement for/Wrapper around `runDB` when jobs need to be queued as part of a database transaction
|
||||||
--
|
--
|
||||||
-- Jobs get immediately executed if the transaction succeeds
|
-- Jobs get immediately executed if the transaction succeeds
|
||||||
runDBJobs act = do
|
runDBJobs act = do
|
||||||
@ -161,7 +161,7 @@ runDBJobs act = do
|
|||||||
forM_ jIds $ flip runReaderT app . writeJobCtl . JobCtlPerform
|
forM_ jIds $ flip runReaderT app . writeJobCtl . JobCtlPerform
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
-- | Blocking!
|
||||||
runDBJobs' :: YesodJobDB UniWorX a -> DB a
|
runDBJobs' :: YesodJobDB UniWorX a -> DB a
|
||||||
runDBJobs' act = do
|
runDBJobs' act = do
|
||||||
(ret, jIds) <- mapReaderT runWriterT act
|
(ret, jIds) <- mapReaderT runWriterT act
|
||||||
|
|||||||
@ -27,6 +27,8 @@ instance Finite HealthCheck
|
|||||||
instance Hashable HealthCheck
|
instance Hashable HealthCheck
|
||||||
instance NFData HealthCheck
|
instance NFData HealthCheck
|
||||||
|
|
||||||
|
-- embedRenderMessage ''UniWorX ''HealthCheck id -- not possible here
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = camelToPathPiece' 2
|
{ constructorTagModifier = camelToPathPiece' 2
|
||||||
} ''HealthCheck
|
} ''HealthCheck
|
||||||
@ -66,9 +68,9 @@ classifyHealthReport HealthMatchingClusterConfig{} = HealthCheckMatchingClusterC
|
|||||||
classifyHealthReport HealthLDAPAdmins{} = HealthCheckLDAPAdmins
|
classifyHealthReport HealthLDAPAdmins{} = HealthCheckLDAPAdmins
|
||||||
classifyHealthReport HealthHTTPReachable{} = HealthCheckHTTPReachable
|
classifyHealthReport HealthHTTPReachable{} = HealthCheckHTTPReachable
|
||||||
classifyHealthReport HealthSMTPConnect{} = HealthCheckSMTPConnect
|
classifyHealthReport HealthSMTPConnect{} = HealthCheckSMTPConnect
|
||||||
classifyHealthReport HealthWidgetMemcached{} = HealthCheckWidgetMemcached
|
classifyHealthReport HealthWidgetMemcached{} = HealthCheckWidgetMemcached -- kein Neustart notwendig
|
||||||
classifyHealthReport HealthActiveJobExecutors{} = HealthCheckActiveJobExecutors
|
classifyHealthReport HealthActiveJobExecutors{} = HealthCheckActiveJobExecutors
|
||||||
classifyHealthReport HealthDoesFlush{} = HealthCheckDoesFlush
|
classifyHealthReport HealthDoesFlush{} = HealthCheckDoesFlush -- evtl. kein Neustart notwendig
|
||||||
|
|
||||||
-- | `HealthReport` classified (`classifyHealthReport`) by badness
|
-- | `HealthReport` classified (`classifyHealthReport`) by badness
|
||||||
--
|
--
|
||||||
@ -76,7 +78,7 @@ classifyHealthReport HealthDoesFlush{} = HealthCheckDoesFlush
|
|||||||
--
|
--
|
||||||
-- Currently all consumers of this type check for @(== HealthSuccess)@; this
|
-- Currently all consumers of this type check for @(== HealthSuccess)@; this
|
||||||
-- needs to be adjusted on a case-by-case basis if new constructors are added
|
-- needs to be adjusted on a case-by-case basis if new constructors are added
|
||||||
data HealthStatus = HealthFailure | HealthSuccess
|
data HealthStatus = HealthFailure | HealthInactive | HealthSuccess
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
||||||
|
|
||||||
instance Universe HealthStatus
|
instance Universe HealthStatus
|
||||||
@ -87,17 +89,30 @@ deriveJSON defaultOptions
|
|||||||
} ''HealthStatus
|
} ''HealthStatus
|
||||||
nullaryPathPiece ''HealthStatus $ camelToPathPiece' 1
|
nullaryPathPiece ''HealthStatus $ camelToPathPiece' 1
|
||||||
|
|
||||||
|
healthOk :: HealthStatus -> Bool
|
||||||
|
healthOk HealthFailure = False
|
||||||
|
healthOk HealthInactive = True
|
||||||
|
healthOk HealthSuccess = True
|
||||||
|
|
||||||
healthReportStatus :: HealthReport -> HealthStatus
|
healthReportStatus :: HealthReport -> HealthStatus
|
||||||
-- ^ Classify `HealthReport` by badness
|
-- ^ Classify `HealthReport` by badness
|
||||||
healthReportStatus = \case
|
healthReportStatus = \case
|
||||||
HealthMatchingClusterConfig False -> HealthFailure
|
HealthMatchingClusterConfig True -> HealthSuccess
|
||||||
HealthHTTPReachable (Just False) -> HealthFailure
|
HealthHTTPReachable (Just True ) -> HealthSuccess
|
||||||
|
HealthHTTPReachable Nothing -> HealthInactive
|
||||||
HealthLDAPAdmins (Just prop )
|
HealthLDAPAdmins (Just prop )
|
||||||
| prop <= 0 -> HealthFailure
|
| prop > 0 -> HealthSuccess
|
||||||
HealthSMTPConnect (Just False) -> HealthFailure
|
HealthLDAPAdmins Nothing -> HealthInactive
|
||||||
|
HealthSMTPConnect (Just True ) -> HealthSuccess
|
||||||
|
HealthSMTPConnect Nothing -> HealthInactive
|
||||||
HealthWidgetMemcached (Just False) -> HealthFailure -- TODO: investigate this failure mode; do we just handle it gracefully?
|
HealthWidgetMemcached (Just False) -> HealthFailure -- TODO: investigate this failure mode; do we just handle it gracefully?
|
||||||
|
HealthWidgetMemcached (Just True ) -> HealthSuccess
|
||||||
|
HealthWidgetMemcached Nothing -> HealthInactive
|
||||||
|
HealthActiveJobExecutors Nothing -> HealthInactive
|
||||||
HealthActiveJobExecutors (Just prop )
|
HealthActiveJobExecutors (Just prop )
|
||||||
| prop <= 0 -> HealthFailure
|
| prop > 0 -> HealthSuccess
|
||||||
HealthDoesFlush mProp
|
HealthDoesFlush Nothing -> HealthInactive
|
||||||
| maybe True (>= 2) mProp -> HealthFailure
|
HealthDoesFlush (Just prop )
|
||||||
_other -> maxBound -- Minimum badness
|
| prop >= 2 -> HealthFailure
|
||||||
|
| otherwise -> HealthSuccess
|
||||||
|
_other -> HealthFailure
|
||||||
|
|||||||
@ -9,6 +9,7 @@ module Utils.PathPiece
|
|||||||
, splitCamel, dropCamel
|
, splitCamel, dropCamel
|
||||||
, camelToPathPiece, camelToPathPiece', camelToPathPiece''
|
, camelToPathPiece, camelToPathPiece', camelToPathPiece''
|
||||||
, nameToPathPiece, nameToPathPiece'
|
, nameToPathPiece, nameToPathPiece'
|
||||||
|
, kebabToCamel
|
||||||
, tuplePathPiece
|
, tuplePathPiece
|
||||||
, pathPieceJSON, pathPieceJSONKey
|
, pathPieceJSON, pathPieceJSONKey
|
||||||
, pathPieceBinary
|
, pathPieceBinary
|
||||||
@ -237,6 +238,11 @@ nameToPathPiece' dropN = camelToPathPiece' dropN . repack . nameBase
|
|||||||
nameToPathPiece :: Textual t => Name -> t
|
nameToPathPiece :: Textual t => Name -> t
|
||||||
nameToPathPiece = nameToPathPiece' 0
|
nameToPathPiece = nameToPathPiece' 0
|
||||||
|
|
||||||
|
-- | convert kebab-case to CamelCase
|
||||||
|
kebabToCamel :: Text -> Text
|
||||||
|
-- kebabToCamel = Text.filter (not . Char.isSpace) . Text.toTitle . Text.replace "-" " " -- eliminates all space
|
||||||
|
kebabToCamel = mconcat . fmap Text.toTitle . Text.split ('-'==) -- preserves existing spaces
|
||||||
|
|
||||||
|
|
||||||
tuplePathPiece :: Int -> DecQ
|
tuplePathPiece :: Int -> DecQ
|
||||||
tuplePathPiece tupleDim = do
|
tuplePathPiece tupleDim = do
|
||||||
|
|||||||
@ -278,12 +278,12 @@ printLetter' pji pdf = do
|
|||||||
insert_ PrintJob {..}
|
insert_ PrintJob {..}
|
||||||
return $ Right (ok, printJobFilename)
|
return $ Right (ok, printJobFilename)
|
||||||
|
|
||||||
reprintPDF :: PrintJobId -> DB (Either Text Text)
|
reprintPDF :: Bool -> PrintJobId -> DB (Either Text Text)
|
||||||
reprintPDF pjid = maybeM (return $ Left "Print job id is unknown.") reprint $ get pjid
|
reprintPDF ignoreReroute pjid = maybeM (return $ Left "Print job id is unknown.") reprint $ get pjid
|
||||||
where
|
where
|
||||||
reprint :: PrintJob -> DB (Either Text Text)
|
reprint :: PrintJob -> DB (Either Text Text)
|
||||||
reprint pj@PrintJob{..} = do
|
reprint pj@PrintJob{..} = do
|
||||||
result <- lprPDF printJobFilename $ LBS.fromStrict printJobFile
|
result <- lprPDF' ignoreReroute printJobFilename $ LBS.fromStrict printJobFile
|
||||||
whenIsRight result $ const $ do
|
whenIsRight result $ const $ do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
insert_ pj{ printJobAcknowledged = Nothing
|
insert_ pj{ printJobAcknowledged = Nothing
|
||||||
@ -460,26 +460,29 @@ encryptPDF pw bs = over _Left (decodeUtf8 . LBS.toStrict) . exit2either <$> read
|
|||||||
|
|
||||||
-- | Internal only, use `printLetter` instead
|
-- | Internal only, use `printLetter` instead
|
||||||
lprPDF :: (MonadHandler m, HasAppSettings (HandlerSite m)) => FilePath -> LBS.ByteString -> m (Either Text Text)
|
lprPDF :: (MonadHandler m, HasAppSettings (HandlerSite m)) => FilePath -> LBS.ByteString -> m (Either Text Text)
|
||||||
lprPDF (sanitizeCmdArg' -> jb) bs = do
|
lprPDF = lprPDF' False
|
||||||
mbLprServerArg <- getLprServerArg
|
|
||||||
case mbLprServerArg of
|
lprPDF' :: (MonadHandler m, HasAppSettings (HandlerSite m)) => Bool -> FilePath -> LBS.ByteString -> m (Either Text Text)
|
||||||
Nothing -> return $ Right "Print command ignored due to setting 'mail-reroute-to' being set."
|
lprPDF' ignoreReroute (sanitizeCmdArg' -> jb) bs = maybeM hdlFail hdlLpr getLprServerArg
|
||||||
Just lprServerArg -> do
|
|
||||||
let pc = setStdin (byteStringInput bs) $
|
|
||||||
proc "lpr" $
|
|
||||||
jobname ++ -- -J jobname -- a name for job identification at printing site
|
|
||||||
[ lprServerArg -- -P queue@hostname:port
|
|
||||||
, "-" -- read from stdin
|
|
||||||
]
|
|
||||||
jobname | null jb = []
|
|
||||||
| otherwise = ["-J " <> jb]
|
|
||||||
exit2either <$> readProcess' pc
|
|
||||||
where
|
where
|
||||||
|
hdlFail = return $ Right "Print command ignored due to setting 'mail-reroute-to' being set."
|
||||||
|
|
||||||
|
hdlLpr lprServerArg = do
|
||||||
|
let pc = setStdin (byteStringInput bs) $
|
||||||
|
proc "lpr" $
|
||||||
|
jobname ++ -- -J jobname -- a name for job identification at printing site
|
||||||
|
[ lprServerArg -- -P queue@hostname:port
|
||||||
|
, "-" -- read from stdin
|
||||||
|
]
|
||||||
|
jobname | null jb = []
|
||||||
|
| otherwise = ["-J " <> jb]
|
||||||
|
exit2either <$> readProcess' pc
|
||||||
|
|
||||||
getLprServerArg = do
|
getLprServerArg = do
|
||||||
rerouteMail <- getsYesod $ view _appMailRerouteTo
|
rerouteMail <- getsYesod $ view _appMailRerouteTo
|
||||||
case rerouteMail of
|
case (ignoreReroute, rerouteMail) of
|
||||||
Just _ -> return Nothing
|
(False, Just _) -> return Nothing
|
||||||
Nothing -> do
|
_ -> do
|
||||||
LprConf{..} <- getsYesod $ view _appLprConf
|
LprConf{..} <- getsYesod $ view _appLprConf
|
||||||
return . Just $ "-P " <> lprQueue <> "@" <> lprHost <> ":" <> show lprPort
|
return . Just $ "-P " <> lprQueue <> "@" <> lprHost <> ":" <> show lprPort
|
||||||
|
|
||||||
|
|||||||
@ -285,9 +285,9 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
<dt .deflist__dt> Ausbilder:innen
|
<dt .deflist__dt> Ausbilder:innen
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
<p>
|
<p>
|
||||||
Ausbilder:innen werden ad hoc pro Kursgruppe festgelegt.
|
Ausbilder:innen werden ad hoc pro Kurs festgelegt.
|
||||||
<br />
|
<br />
|
||||||
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.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
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.
|
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
|
|||||||
<br />
|
<br />
|
||||||
Eine vorherige Anmeldung zur Kursart ist Voraussetzung.
|
Eine vorherige Anmeldung zur Kursart ist Voraussetzung.
|
||||||
<p>
|
<p>
|
||||||
Die Anmeldung kann pro Kursgruppe zeitlich beschränkt werden.
|
Die Anmeldung kann pro Kurs zeitlich beschränkt werden.
|
||||||
<p>
|
<p>
|
||||||
Kursgruppen können mit einer <i>Registrierungs-Gruppe</i> versehen werden.
|
Kurse können mit einer <i>Registrierungs-Gruppe</i> versehen werden.
|
||||||
Es handelt sich hierbei um einen beliebig wählbaren Text, der ansonsten keine Bedeutung hat.
|
Es handelt sich hierbei um einen beliebig wählbaren Text, der ansonsten keine Bedeutung hat.
|
||||||
<br />
|
<br />
|
||||||
Lernenden wird die Anmeldung nur in <i>einem Kurs pro Registrierungs-Gruppe</i> erlaubt.
|
Lernenden wird die Anmeldung nur in <i>einem Kurs pro Registrierungs-Gruppe</i> erlaubt.
|
||||||
|
|
||||||
Leere Registrierungs-Gruppen (d.h. es wurde keine Registrierungs-Gruppe angegeben) zählen hierbei als verschieden.
|
Leere Registrierungs-Gruppen (d.h. es wurde keine Registrierungs-Gruppe angegeben) zählen hierbei als verschieden.
|
||||||
<p>
|
<p>
|
||||||
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.
|
||||||
|
|
||||||
<dt .deflist__dt> ^{newFeat 2019 10 10} Nachmeldung
|
<dt .deflist__dt> ^{newFeat 2019 10 10} Nachmeldung
|
||||||
<dt .deflist__dd>
|
<dt .deflist__dd>
|
||||||
|
|||||||
@ -7,3 +7,6 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
^{pjTable}
|
^{pjTable}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
^{modal "APC Konfiguration" (Right lprWgt)}
|
||||||
Reference in New Issue
Block a user