From 1032e4e7473d6b2de4f72c7fc7da59865bede4a8 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Fri, 20 Oct 2023 22:16:40 +0100 Subject: [PATCH] activity: more presentable cards, fixes fixed some missing data (being stored and being shown), improved layout, also usernames are now injected by the route. --- api-activities.go | 12 ++++- api-invites.go | 8 +++- html/admin.html | 2 +- lang/admin/en-us.json | 7 +-- models.go | 18 ++++---- router.go | 2 +- storage.go | 2 +- ts/modules/activity.ts | 99 ++++++++++++++++++++++++++++++++---------- userdaemon.go | 6 +-- 9 files changed, 113 insertions(+), 43 deletions(-) diff --git a/api-activities.go b/api-activities.go index 305a66f..7620307 100644 --- a/api-activities.go +++ b/api-activities.go @@ -89,7 +89,7 @@ func activitySourceToString(v ActivitySource) string { // @Produce json // @Param GetActivitiesDTO body GetActivitiesDTO true "search parameters" // @Success 200 {object} GetActivitiesRespDTO -// @Router /activity [get] +// @Router /activity [post] // @Security Bearer // @tags Activity func (app *appContext) GetActivities(gc *gin.Context) { @@ -138,6 +138,16 @@ func (app *appContext) GetActivities(gc *gin.Context) { Value: act.Value, Time: act.Time.Unix(), } + user, status, err := app.jf.UserByID(act.UserID, false) + if status == 200 && err == nil { + resp.Activities[i].Username = user.Name + } + if (act.SourceType == ActivityUser || act.SourceType == ActivityAdmin) && act.Source != "" { + user, status, err = app.jf.UserByID(act.Source, false) + if status == 200 && err == nil { + resp.Activities[i].SourceUsername = user.Name + } + } } gc.JSON(200, resp) diff --git a/api-invites.go b/api-invites.go index 356c15a..c537a9c 100644 --- a/api-invites.go +++ b/api-invites.go @@ -90,6 +90,7 @@ func (app *appContext) checkInvites() { Type: ActivityDeleteInvite, SourceType: ActivityDaemon, InviteCode: data.Code, + Value: data.Label, Time: time.Now(), }) } @@ -141,6 +142,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool Type: ActivityDeleteInvite, SourceType: ActivityDaemon, InviteCode: code, + Value: inv.Label, Time: time.Now(), }) } else if used { @@ -153,6 +155,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool Type: ActivityDeleteInvite, SourceType: ActivityDaemon, InviteCode: code, + Value: inv.Label, Time: time.Now(), }) } else if newInv.RemainingUses != 0 { @@ -460,8 +463,7 @@ func (app *appContext) DeleteInvite(gc *gin.Context) { var req deleteInviteDTO gc.BindJSON(&req) app.debug.Printf("%s: Deletion requested", req.Code) - var ok bool - _, ok = app.storage.GetInvitesKey(req.Code) + inv, ok := app.storage.GetInvitesKey(req.Code) if ok { app.storage.DeleteInvitesKey(req.Code) @@ -470,6 +472,8 @@ func (app *appContext) DeleteInvite(gc *gin.Context) { Type: ActivityDeleteInvite, SourceType: ActivityAdmin, Source: gc.GetString("jfId"), + InviteCode: req.Code, + Value: inv.Label, Time: time.Now(), }) diff --git a/html/admin.html b/html/admin.html index 81ffce3..6dd523e 100644 --- a/html/admin.html +++ b/html/admin.html @@ -778,7 +778,7 @@ {{ .strings.aboutProgram }} {{ .strings.userProfiles }} -
+
Account Created: "hrfee" diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json index 75673a9..d81b750 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -134,8 +134,8 @@ "builtBy": "Built By", "loginNotAdmin": "Not an Admin?", "referrer": "Referrer", - "accountLinked": "{user}: {contactMethod} linked", - "accountUnlinked": "{user}: {contactMethod} removed", + "accountLinked": "{contactMethod} linked: {user}", + "accountUnlinked": "{contactMethod} removed: {user}", "accountResetPassword": "{user} reset their password", "accountChangedPassword": "{user} changed their password", "accountCreated": "Account created: {user}", @@ -146,7 +146,8 @@ "userDeleted": "User was deleted.", "userDisabled": "User was disabled", "inviteCreated": "Invite created: {invite}", - "inviteDeleted": "Invite deleted: {invite}" + "inviteDeleted": "Invite deleted: {invite}", + "inviteExpired": "Invite expired: {invite}" }, "notifications": { "changedEmailAddress": "Changed email address of {n}.", diff --git a/models.go b/models.go index 0a4afa6..b8573fe 100644 --- a/models.go +++ b/models.go @@ -432,14 +432,16 @@ type EnableDisableReferralDTO struct { } type ActivityDTO struct { - ID string `json:"id"` - Type string `json:"type"` - UserID string `json:"user_id"` - SourceType string `json:"source_type"` - Source string `json:"source"` - InviteCode string `json:"invite_code"` - Value string `json:"value"` - Time int64 `json:"time"` + ID string `json:"id"` + Type string `json:"type"` + UserID string `json:"user_id"` + Username string `json:"username"` + SourceType string `json:"source_type"` + Source string `json:"source"` + SourceUsername string `json:"source_username"` + InviteCode string `json:"invite_code"` + Value string `json:"value"` + Time int64 `json:"time"` } type GetActivitiesDTO struct { diff --git a/router.go b/router.go index edcbfdb..db34361 100644 --- a/router.go +++ b/router.go @@ -232,7 +232,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile) } - api.GET(p+"/activity", app.GetActivities) + api.POST(p+"/activity", app.GetActivities) if userPageEnabled { user.GET("/details", app.MyDetails) diff --git a/storage.go b/storage.go index bfee54e..a3984a4 100644 --- a/storage.go +++ b/storage.go @@ -52,7 +52,7 @@ type Activity struct { UserID string // ID of target user. For account creation, this will be the newly created account SourceType ActivitySource Source string - InviteCode string // Only set for ActivityCreation + InviteCode string // Set for ActivityCreation, create/deleteInvite Value string // Used for ActivityContactLinked, "email/discord/telegram/matrix", and Create/DeleteInvite, where it's the label. Time time.Time } diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index 104af07..940518e 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -1,4 +1,4 @@ -import { _get, toDateString } from "../modules/common.js"; +import { _post, toDateString } from "../modules/common.js"; export interface activity { id: string; @@ -9,6 +9,8 @@ export interface activity { invite_code: string; value: string; time: number; + username: string; + source_username: string; } var activityTypeMoods = { @@ -37,25 +39,50 @@ export class Activity { // FIXME: Add "implements" private _expiryTypeBadge: HTMLElement; private _act: activity; + _genUserLink = (): string => { + return `${this._act.username || this._act.user_id.substring(0, 5)}`; + } + + _genSrcUserLink = (): string => { + return `${this._act.source_username || this._act.source.substring(0, 5)}`; + } + + private _renderInvText = (): string => { return `${this.value || this.invite_code || "???"}`; } + + private _genInvLink = (): string => { + return `${this._renderInvText()}`; + } + get type(): string { return this._act.type; } set type(v: string) { this._act.type = v; let mood = activityTypeMoods[v]; // 1 = positive, 0 = neutral, -1 = negative + this._card.classList.remove("~warning"); + this._card.classList.remove("~neutral"); + this._card.classList.remove("~urge"); - for (let i = 0; i < moodColours.length; i++) { + if (mood == -1) { + this._card.classList.add("~warning"); + } else if (mood == 0) { + this._card.classList.add("~neutral"); + } else if (mood == 1) { + this._card.classList.add("~urge"); + } + + /* for (let i = 0; i < moodColours.length; i++) { if (i-1 == mood) this._card.classList.add(moodColours[i]); else this._card.classList.remove(moodColours[i]); - } + } */ if (this.type == "changePassword" || this.type == "resetPassword") { let innerHTML = ``; if (this.type == "changePassword") innerHTML = window.lang.strings("accountChangedPassword"); else innerHTML = window.lang.strings("accountResetPassword"); - innerHTML = innerHTML.replace("{user}", `FIXME`); + innerHTML = innerHTML.replace("{user}", this._genUserLink()); this._title.innerHTML = innerHTML; } else if (this.type == "contactLinked" || this.type == "contactUnlinked") { - let platform = this._act.type; + let platform = this.value; if (platform == "email") { platform = window.lang.strings("emailAddress"); } else { @@ -64,40 +91,46 @@ export class Activity { // FIXME: Add "implements" let innerHTML = ``; if (this.type == "contactLinked") innerHTML = window.lang.strings("accountLinked"); else innerHTML = window.lang.strings("accountUnlinked"); - innerHTML = innerHTML.replace("{user}", `FIXME`).replace("{contactMethod}", platform); + innerHTML = innerHTML.replace("{user}", this._genUserLink()).replace("{contactMethod}", platform); this._title.innerHTML = innerHTML; } else if (this.type == "creation") { - this._title.innerHTML = window.lang.strings("accountCreated").replace("{user}", `FIXME`); + this._title.innerHTML = window.lang.strings("accountCreated").replace("{user}", this._genUserLink()); if (this.source_type == "user") { - this._referrer.innerHTML = `${window.lang.strings("referrer")}FIXME`; + this._referrer.innerHTML = `${window.lang.strings("referrer")}${this._genSrcUserLink()}`; } else { this._referrer.textContent = ``; } } else if (this.type == "deletion") { if (this.source_type == "daemon") { - this._title.innerHTML = window.lang.strings("accountExpired").replace("{user}", `FIXME`); + this._title.innerHTML = window.lang.strings("accountExpired").replace("{user}", this._genUserLink()); this._expiryTypeBadge.classList.add("~critical"); - this._expiryTypeBadge.classList.remove("~warning"); + this._expiryTypeBadge.classList.remove("~info"); this._expiryTypeBadge.textContent = window.lang.strings("deleted"); } else { - this._title.innerHTML = window.lang.strings("accountDeleted").replace("{user}", `FIXME`); + this._title.innerHTML = window.lang.strings("accountDeleted").replace("{user}", this._genUserLink()); } } else if (this.type == "enabled") { - this._title.innerHTML = window.lang.strings("accountReEnabled").replace("{user}", `FIXME`); + this._title.innerHTML = window.lang.strings("accountReEnabled").replace("{user}", this._genUserLink()); } else if (this.type == "disabled") { if (this.source_type == "daemon") { - this._title.innerHTML = window.lang.strings("accountExpired").replace("{user}", `FIXME`); - this._expiryTypeBadge.classList.add("~warning"); + this._title.innerHTML = window.lang.strings("accountExpired").replace("{user}", this._genUserLink()); + this._expiryTypeBadge.classList.add("~info"); this._expiryTypeBadge.classList.remove("~critical"); this._expiryTypeBadge.textContent = window.lang.strings("disabled"); } else { - this._title.innerHTML = window.lang.strings("accountDisabled").replace("{user}", `FIXME`); + this._title.innerHTML = window.lang.strings("accountDisabled").replace("{user}", this._genUserLink()); } } else if (this.type == "createInvite") { - this._title.innerHTML = window.lang.strings("inviteCreated").replace("{invite}", `${this.value || this.invite_code}`); + this._title.innerHTML = window.lang.strings("inviteCreated").replace("{invite}", this._genInvLink()); } else if (this.type == "deleteInvite") { + let innerHTML = ``; + if (this.source_type == "daemon") { + innerHTML = window.lang.strings("inviteExpired"); + } else { + innerHTML = window.lang.strings("inviteDeleted"); + } - this._title.innerHTML = window.lang.strings("inviteDeleted").replace("{invite}", this.value || this.invite_code); + this._title.innerHTML = innerHTML.replace("{invite}", this._renderInvText()); } /*} else if (this.source_type == "admin") { @@ -130,14 +163,22 @@ export class Activity { // FIXME: Add "implements" this._act.value = v; } + get source(): string { return this._act.source; } + set source(v: string) { + this._act.source = v; + } + constructor(act: activity) { this._card = document.createElement("div"); - this._card.classList.add("card", "@low"); + this._card.classList.add("card", "@low", "my-2"); this._card.innerHTML = ` -
- - +
+ +
+ + +
@@ -156,6 +197,10 @@ export class Activity { // FIXME: Add "implements" this._referrer = this._card.querySelector(".activity-referrer"); this._expiryTypeBadge = this._card.querySelector(".activity-expiry-type"); + document.addEventListener("timefmt-change", () => { + this.time = this.time; + }); + this.update(act); } @@ -164,6 +209,8 @@ export class Activity { // FIXME: Add "implements" this._act = act; this.source_type = act.source_type; this.invite_code = act.invite_code; + this.time = act.time; + this.source = act.source; this.value = act.value; this.type = act.type; } @@ -179,7 +226,13 @@ export class activityList { private _activityList: HTMLElement; reload = () => { - _get("/activity", null, (req: XMLHttpRequest) => { + let send = { + "type": [], + "limit": 30, + "page": 0, + "ascending": false + } + _post("/activity", send, (req: XMLHttpRequest) => { if (req.readyState != 4) return; if (req.status != 200) { window.notifications.customError("loadActivitiesError", window.lang.notif("errorLoadActivities")); @@ -193,7 +246,7 @@ export class activityList { const activity = new Activity(act); this._activityList.appendChild(activity.asElement()); } - }); + }, true); } constructor() { diff --git a/userdaemon.go b/userdaemon.go index 5ac7dcd..0e5aa47 100644 --- a/userdaemon.go +++ b/userdaemon.go @@ -61,10 +61,10 @@ func (app *appContext) checkUsers() { return } mode := "disable" - termPlural := "Disabling" + term := "Disabling" if app.config.Section("user_expiry").Key("behaviour").MustString("disable_user") == "delete_user" { mode = "delete" - termPlural = "Deleting" + term = "Deleting" } contact := false if messagesEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true) { @@ -95,7 +95,7 @@ func (app *appContext) checkUsers() { app.storage.DeleteUserExpiryKey(expiry.JellyfinID) continue } - app.info.Printf("%s expired user \"%s\"", termPlural, user.Name) + app.info.Printf("%s expired user \"%s\"", term, user.Name) // Record activity activity := Activity{