From 379afa955441a1995d7036f4ad1d3dd495033442 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 17 Mar 2025 22:48:21 +0100 Subject: [PATCH] Improve Adminpanel with local logs (#15404) * log armoire, quoest response and cron events to history * show user history in admin panel * allow stats to be edited from admin panel * Improve admin panel stats input * improve setting client in history * fix tests * fix lint * fix armoire buying issue * Improve hero saving * Formatting fix * Improve user history logging * allow class to be changed from admin panel * make terminating subscriptions easier * support decimal extraMonths * Fix editing some achievements in admin panel * log if a user invites party to quest * Log more quest events into user history * make userhistory length configurable * fix some numbered achievements * fix extraMonths field * Automatically set up group plan subs with admin panel * show party info nicer in admin panel * improve admin panel sub handling * add missing brace * display when there are unsaved changes * fix setting group plan * fix showing group id * Display group plan info in admin panel * fix setting hourglass promo date * Improve termination handling in admin panel * reload data after certain save events in admin panel * remove console * fix plan.extraMonths not being reset if terminating a sub * add more options when cancelling subs * reload data after group plan change * Add a way to remove users from a party * fix issue with removing user from party * pass party id correctly * correctly call async function * Improve sub display in admin panel * fix line length * fix line * shorter * plaid * fix(lint): vue code style --------- Co-authored-by: Kalista Payne --- .../hall/GET-hall_heroes_heroId.test.js | 1 + .../hall/PUT-hall_heores_heroId.test.js | 1 + .../src/components/admin-panel/index.vue | 12 +- .../components/admin-panel/mixins/saveHero.js | 25 +- .../src/components/admin-panel/search.vue | 14 +- .../admin-panel/user-support/achievements.vue | 189 ++++++----- .../user-support/contributorDetails.vue | 24 +- .../admin-panel/user-support/cronAndAuth.vue | 41 ++- .../user-support/customizationsOwned.vue | 15 +- .../admin-panel/user-support/index.vue | 63 ++++ .../admin-panel/user-support/itemsOwned.vue | 13 +- .../user-support/partyAndQuest.vue | 48 ++- .../user-support/privilegesAndGems.vue | 20 +- .../admin-panel/user-support/stats-row.vue | 72 +++++ .../admin-panel/user-support/stats.vue | 286 +++++++++++++++++ .../user-support/subscriptionAndPerks.vue | 301 +++++++++++++++++- .../admin-panel/user-support/userHistory.vue | 263 +++++++++++++++ .../admin-panel/user-support/userProfile.vue | 19 +- .../src/components/settings/subscription.vue | 1 - .../client/src/store/actions/adminPanel.js | 6 + website/client/src/store/actions/hall.js | 6 + website/server/controllers/api-v3/groups.js | 52 +-- website/server/controllers/api-v3/hall.js | 149 ++++++++- website/server/controllers/api-v3/quests.js | 47 ++- website/server/controllers/api-v3/user.js | 11 + website/server/controllers/api-v4/admin.js | 45 ++- website/server/libs/cron.js | 5 + website/server/libs/groups.js | 58 ++++ website/server/models/group.js | 9 +- website/server/models/user/hooks.js | 18 +- website/server/models/user/schema.js | 1 + website/server/models/userHistory.js | 129 ++++++++ 32 files changed, 1743 insertions(+), 201 deletions(-) create mode 100644 website/client/src/components/admin-panel/user-support/stats-row.vue create mode 100644 website/client/src/components/admin-panel/user-support/stats.vue create mode 100644 website/client/src/components/admin-panel/user-support/userHistory.vue create mode 100644 website/server/libs/groups.js create mode 100644 website/server/models/userHistory.js diff --git a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js index c83e4cbaf1..6d02f1a2a4 100644 --- a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js +++ b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js @@ -10,6 +10,7 @@ describe('GET /heroes/:heroId', () => { const heroFields = [ '_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements', + 'stats', ]; before(async () => { diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js index 020b5a56ae..b459664c33 100644 --- a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js +++ b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js @@ -11,6 +11,7 @@ describe('PUT /heroes/:heroId', () => { const heroFields = [ '_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements', + 'stats', ]; before(async () => { diff --git a/website/client/src/components/admin-panel/index.vue b/website/client/src/components/admin-panel/index.vue index c31d729042..e9c9436153 100644 --- a/website/client/src/components/admin-panel/index.vue +++ b/website/client/src/components/admin-panel/index.vue @@ -92,8 +92,6 @@ export default { params: { userIdentifier }, }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { - // the admin has requested that the same user be displayed again so reload the page - // (e.g., if they changed their mind about changes they were making) this.$router.go(); } }); @@ -101,14 +99,16 @@ export default { async loadUser (userIdentifier) { const id = userIdentifier || this.user._id; - - this.$router.push({ + if (this.$router.currentRoute.name === 'adminPanelUser') { + await this.$router.push({ + name: 'adminPanel', + }); + } + await this.$router.push({ name: 'adminPanelUser', params: { userIdentifier: id }, }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { - // the admin has requested that the same user be displayed again so reload the page - // (e.g., if they changed their mind about changes they were making) this.$router.go(); } }); diff --git a/website/client/src/components/admin-panel/mixins/saveHero.js b/website/client/src/components/admin-panel/mixins/saveHero.js index 8bbef7170b..7f8061ce8e 100644 --- a/website/client/src/components/admin-panel/mixins/saveHero.js +++ b/website/client/src/components/admin-panel/mixins/saveHero.js @@ -1,6 +1,15 @@ +import VueRouter from 'vue-router'; + +const { isNavigationFailure, NavigationFailureType } = VueRouter; + export default { methods: { - async saveHero ({ hero, msg = 'User', clearData }) { + async saveHero ({ + hero, + msg = 'User', + clearData, + reloadData, + }) { await this.$store.dispatch('hall:updateHero', { heroDetails: hero }); await this.$store.dispatch('snackbars:add', { title: '', @@ -14,6 +23,20 @@ export default { // The admin should re-fetch the data if they need to keep working on that user. this.$emit('clear-data'); this.$router.push({ name: 'adminPanel' }); + } else if (reloadData) { + if (this.$router.currentRoute.name === 'adminPanelUser') { + await this.$router.push({ + name: 'adminPanel', + }); + } + await this.$router.push({ + name: 'adminPanelUser', + params: { userIdentifier: hero._id }, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + this.$router.go(); + } + }); } }, }, diff --git a/website/client/src/components/admin-panel/search.vue b/website/client/src/components/admin-panel/search.vue index 736de6ca5e..f6fcaf2ee0 100644 --- a/website/client/src/components/admin-panel/search.vue +++ b/website/client/src/components/admin-panel/search.vue @@ -7,7 +7,11 @@ > Could not find any matching users. - +
: - {{ itemText(item) }} + {{ item.text || item.key }} - {{ item.key }}
: - {{ itemText(item) }} + {{ item.text || item.key }} - {{ item.key }}
{ self.expandItemType[itemType] = false; }); + const { singularTextKey } = achievementItem; + if (singularTextKey !== undefined) { + return i18n.t(singularTextKey, { count }); + } + return ''; } export default { @@ -241,26 +192,34 @@ export default { }, nestedAchievementKeys: ['quests', 'ultimateGearSets'], integerTypes: ['streak', 'perfect', 'birthday', 'habiticaDays', 'habitSurveys', 'habitBirthdays', - 'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests'], + 'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests', + 'rebirths', 'rebirthLevel', 'greeting', 'spookySparkles', 'nye', 'costumeContests', 'congrats', + 'getwell', 'beastMasterCount', 'mountMasterCount', 'triadBingoCount', + ], + cardTypes: ['greeting', 'birthday', 'valentine', 'goodluck', 'thankyou', 'greeting', 'nye', + 'congrats', 'getwell'], achievements: [], nestedAchievements: {}, }; }, watch: { resetCounter () { - resetData(this); + this.resetData(); }, }, mounted () { - resetData(this); + this.resetData(); }, methods: { async saveItem (item) { - // prepare the item's new value and path for being saved - this.hero.achievementPath = item.path; - this.hero.achievementVal = item.value; - - await this.saveHero({ hero: this.hero, msg: item.path }); + await this.saveHero({ + hero: { + _id: this.hero._id, + achievementPath: item.path, + achievementVal: item.value, + }, + msg: item.path, + }); item.modified = false; }, enableValueChange (item) { @@ -270,14 +229,84 @@ export default { item.value = !item.value; } }, - itemText (item) { - if (item.key === 'npc') { - return this.$t('npcAchievementName', { key: this.hero.backer && this.hero.backer.npc }); + resetData () { + this.collateItemData(); + this.nestedAchievementKeys.forEach(itemType => { this.expandItemType[itemType] = false; }); + }, + collateItemData () { + const achievements = []; + const nestedAchievements = {}; + const basePath = 'achievements'; + const ownedAchievements = this.hero.achievements; + const allAchievements = content.achievements; + + const ownedKeys = Object.keys(ownedAchievements).sort(); + for (const key of ownedKeys) { + const value = ownedAchievements[key]; + let contentKey = key; + if (this.cardTypes.indexOf(key) !== -1) { + contentKey += 'Cards'; + } + if (typeof value === 'object') { + nestedAchievements[key] = []; + for (const nestedKey of Object.keys(value)) { + const valueIsInteger = this.integerTypes.includes(key); + let text = nestedKey; + if (allAchievements[key] && allAchievements[key][contentKey]) { + text = getText(allAchievements[key][contentKey]); + } + let notes = ''; + if (allAchievements[key] && allAchievements[key][contentKey]) { + notes = getNotes(allAchievements[key][contentKey], ownedAchievements[key]); + } + nestedAchievements[key].push({ + key: nestedKey, + text, + notes, + achievementType: key, + modified: false, + path: `${basePath}.${key}.${nestedKey}`, + value: value[nestedKey], + valueIsInteger, + }); + } + } else { + const valueIsInteger = this.integerTypes.includes(key); + achievements.push({ + key, + text: getText(allAchievements[contentKey]), + notes: getNotes(allAchievements[contentKey], ownedAchievements[key]), + modified: false, + path: `${basePath}.${key}`, + value: ownedAchievements[key], + valueIsInteger, + }); + } } - if (item.key === 'kickstarter') { - return this.$t('kickstartName', { key: this.hero.backer && this.hero.backer.tier }); + + const allKeys = Object.keys(allAchievements).sort(); + + for (const key of allKeys) { + if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) { + const ownedKey = key.replace('Cards', ''); + if (ownedAchievements[ownedKey] === undefined) { + const valueIsInteger = this.integerTypes.includes(ownedKey); + achievements.push({ + key: ownedKey, + text: getText(allAchievements[key]), + notes: getNotes(allAchievements[key], 0), + modified: false, + path: `${basePath}.${ownedKey}`, + value: valueIsInteger ? 0 : false, + valueIsInteger, + neverOwned: true, + }); + } + } } - return item.text || item.key; + + this.achievements = achievements; + this.nestedAchievements = nestedAchievements; }, }, }; diff --git a/website/client/src/components/admin-panel/user-support/contributorDetails.vue b/website/client/src/components/admin-panel/user-support/contributorDetails.vue index 6d0ee898ad..7dfdcb0bf3 100644 --- a/website/client/src/components/admin-panel/user-support/contributorDetails.vue +++ b/website/client/src/components/admin-panel/user-support/contributorDetails.vue @@ -1,5 +1,12 @@