diff --git a/api-invites.go b/api-invites.go index e6d92f4..356c15a 100644 --- a/api-invites.go +++ b/api-invites.go @@ -263,6 +263,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { SourceType: ActivityAdmin, Source: gc.GetString("jfId"), InviteCode: invite.Code, + Value: invite.Label, Time: time.Now(), }) diff --git a/html/admin.html b/html/admin.html index 40e6037..81ffce3 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 c9cffef..75673a9 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -55,6 +55,8 @@ "reset": "Reset", "donate": "Donate", "unlink": "Unlink Account", + "deleted": "Deleted", + "disabled": "Disabled", "sendPWR": "Send Password Reset", "noResultsFound": "No Results Found", "contactThrough": "Contact through:", @@ -131,7 +133,20 @@ "buildTime": "Build Time", "builtBy": "Built By", "loginNotAdmin": "Not an Admin?", - "referrer": "Referrer" + "referrer": "Referrer", + "accountLinked": "{user}: {contactMethod} linked", + "accountUnlinked": "{user}: {contactMethod} removed", + "accountResetPassword": "{user} reset their password", + "accountChangedPassword": "{user} changed their password", + "accountCreated": "Account created: {user}", + "accountDeleted": "Account deleted: {user}", + "accountDisabled": "Account disabled: {user}", + "accountReEnabled": "Account re-enabled: {user}", + "accountExpired": "Account expired: {user}", + "userDeleted": "User was deleted.", + "userDisabled": "User was disabled", + "inviteCreated": "Invite created: {invite}", + "inviteDeleted": "Invite deleted: {invite}" }, "notifications": { "changedEmailAddress": "Changed email address of {n}.", @@ -168,6 +183,7 @@ "errorApplyUpdate": "Failed to apply update, try manually.", "errorCheckUpdate": "Failed to check for update.", "errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.", + "errorLoadActivities": "Failed to load activities.", "updateAvailable": "A new update is available, check settings.", "noUpdatesAvailable": "No new updates available." }, diff --git a/storage.go b/storage.go index 5d5af8d..bfee54e 100644 --- a/storage.go +++ b/storage.go @@ -53,7 +53,7 @@ type Activity struct { SourceType ActivitySource Source string InviteCode string // Only set for ActivityCreation - Value string // Used for ActivityContactLinked, "email/discord/telegram/matrix" + Value string // Used for ActivityContactLinked, "email/discord/telegram/matrix", and Create/DeleteInvite, where it's the label. Time time.Time } diff --git a/ts/admin.ts b/ts/admin.ts index 78010d1..1a8109f 100644 --- a/ts/admin.ts +++ b/ts/admin.ts @@ -5,6 +5,7 @@ import { Tabs } from "./modules/tabs.js"; import { inviteList, createInvite } from "./modules/invites.js"; import { accountsList } from "./modules/accounts.js"; import { settingsList } from "./modules/settings.js"; +import { activityList } from "./modules/activity.js"; import { ProfileEditor } from "./modules/profiles.js"; import { _get, _post, notificationBox, whichAnimationEvent } from "./modules/common.js"; import { Updater } from "./modules/update.js"; @@ -89,6 +90,8 @@ var inviteCreator = new createInvite(); var accounts = new accountsList(); +var activity = new activityList(); + window.invites = new inviteList(); var settings = new settingsList(); @@ -122,7 +125,7 @@ const tabs: { url: string, reloader: () => void }[] = [ }, { url: "activity", - reloader: () => {console.log("FIXME: Reload Activity")} + reloader: activity.reload }, { url: "settings", @@ -171,6 +174,7 @@ const login = new Login(window.modals.login as Modal, "/", window.loginAppearanc login.onLogin = () => { console.log("Logged in."); window.updater = new Updater(); + // FIXME: Decide whether to autoload activity or not setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000); const currentTab = window.tabs.current; switch (currentTab) { @@ -183,7 +187,9 @@ login.onLogin = () => { case "settings": settings.reload(); break; - // FIXME: Reload activity + case "activity": // FIXME: fix URL clash with route + activity.reload(); + break; } } diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index 14e7fef..104af07 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -1,3 +1,5 @@ +import { _get, toDateString } from "../modules/common.js"; + export interface activity { id: string; type: string; @@ -28,9 +30,11 @@ export class Activity { // FIXME: Add "implements" private _card: HTMLElement; private _title: HTMLElement; private _time: HTMLElement; + private _timeUnix: number; private _sourceType: HTMLElement; private _source: HTMLElement; private _referrer: HTMLElement; + private _expiryTypeBadge: HTMLElement; private _act: activity; get type(): string { return this._act.type; } @@ -43,24 +47,87 @@ export class Activity { // FIXME: Add "implements" 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`); + this._title.innerHTML = innerHTML; + } else if (this.type == "contactLinked" || this.type == "contactUnlinked") { + let platform = this._act.type; + if (platform == "email") { + platform = window.lang.strings("emailAddress"); + } else { + platform = platform.charAt(0).toUpperCase() + platform.slice(1); + } + 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); + this._title.innerHTML = innerHTML; + } else if (this.type == "creation") { + this._title.innerHTML = window.lang.strings("accountCreated").replace("{user}", `FIXME`); + if (this.source_type == "user") { + this._referrer.innerHTML = `${window.lang.strings("referrer")}FIXME`; + } 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._expiryTypeBadge.classList.add("~critical"); + this._expiryTypeBadge.classList.remove("~warning"); + this._expiryTypeBadge.textContent = window.lang.strings("deleted"); + } else { + this._title.innerHTML = window.lang.strings("accountDeleted").replace("{user}", `FIXME`); + } + } else if (this.type == "enabled") { + this._title.innerHTML = window.lang.strings("accountReEnabled").replace("{user}", `FIXME`); + } 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._expiryTypeBadge.classList.remove("~critical"); + this._expiryTypeBadge.textContent = window.lang.strings("disabled"); + } else { + this._title.innerHTML = window.lang.strings("accountDisabled").replace("{user}", `FIXME`); + } + } else if (this.type == "createInvite") { + this._title.innerHTML = window.lang.strings("inviteCreated").replace("{invite}", `${this.value || this.invite_code}`); + } else if (this.type == "deleteInvite") { + + this._title.innerHTML = window.lang.strings("inviteDeleted").replace("{invite}", this.value || this.invite_code); + } + + /*} else if (this.source_type == "admin") { + // FIXME: Handle contactLinked/Unlinked, creation/deletion, enable/disable, createInvite/deleteInvite + } else if (this.source_type == "anon") { + this._referrer.innerHTML = ``; + } else if (this.source_type == "daemon") { + // FIXME: Handle deleteInvite, disabled, deletion + }*/ + } + + get time(): number { return this._timeUnix; } + set time(v: number) { + this._timeUnix = v; + this._time.textContent = toDateString(new Date(v*1000)); } get source_type(): string { return this._act.source_type; } set source_type(v: string) { this._act.source_type = v; - if (v == "user") { - if (this.type == "creation") { - this._referrer.innerHTML = `${window.lang.strings("referrer")}FIXME`; - } else if (this.type == "contactLinked" || this.type == "contactUnlinked" || this.type == "changePassword" || this.type == "resetPassword") { - // FIXME: Reflect in title - } - } else if (v == "admin") { - // FIXME: Handle contactLinked/Unlinked, creation/deletion, enable/disable, createInvite/deleteInvite - } else if (v == "anon") { - this._referrer.innerHTML = ``; - } else if (v == "daemon") { - // FIXME: Handle deleteInvite, disabled, deletion - } + } + + get invite_code(): string { return this._act.invite_code; } + set invite_code(v: string) { + this._act.invite_code = v; + } + + get value(): string { return this._act.value; } + set value(v: string) { + this._act.value = v; } constructor(act: activity) { @@ -69,7 +136,7 @@ export class Activity { // FIXME: Add "implements" this._card.classList.add("card", "@low"); this._card.innerHTML = `
- +
@@ -87,6 +154,7 @@ export class Activity { // FIXME: Add "implements" this._sourceType = this._card.querySelector(".activity-source-type"); this._source = this._card.querySelector(".activity-source"); this._referrer = this._card.querySelector(".activity-referrer"); + this._expiryTypeBadge = this._card.querySelector(".activity-expiry-type"); this.update(act); } @@ -94,9 +162,41 @@ export class Activity { // FIXME: Add "implements" update = (act: activity) => { // FIXME this._act = act; + this.source_type = act.source_type; + this.invite_code = act.invite_code; + this.value = act.value; this.type = act.type; } asElement = () => { return this._card; }; } +interface ActivitiesDTO { + activities: activity[]; +} + +export class activityList { + private _activityList: HTMLElement; + + reload = () => { + _get("/activity", null, (req: XMLHttpRequest) => { + if (req.readyState != 4) return; + if (req.status != 200) { + window.notifications.customError("loadActivitiesError", window.lang.notif("errorLoadActivities")); + return; + } + + let resp = req.response as ActivitiesDTO; + this._activityList.textContent = ``; + + for (let act of resp.activities) { + const activity = new Activity(act); + this._activityList.appendChild(activity.asElement()); + } + }); + } + + constructor() { + this._activityList = document.getElementById("activity-card-list"); + } +}