From 2c59a5cc64d51a73a723b0736bed3ed26719247e Mon Sep 17 00:00:00 2001
From: tommy
Date: Fri, 8 May 2026 18:08:29 -0400
Subject: [PATCH 01/13] rebuild moderation as a /moderation route with
activity, lookup, moderators, and server-owner tabs
Replaces the floating admin panel with a real route. Tabs gate by permission:
activity feed with stats, smart lookup that detects steam id / objectid,
moderator directory with per-mod drilldown and reverse window, and a new
server-owner self-invalidate surface for /so_moderate. Adds cross-page bulk
selection with named saved selections, and a recap-confirm step for ban,
unban, and reverse actions.
---
src/App.vue | 11 +-
src/api/offstylesApi.ts | 19 +
.../Moderation/AdminModerationPanel.vue | 770 ------------------
src/components/Moderation/ModerationModal.vue | 46 +-
.../Moderation/Panel/ActionBadge.vue | 37 +
.../Moderation/Panel/ActivityTab.vue | 261 ++++++
.../Moderation/Panel/BulkSelectionTray.vue | 278 +++++++
.../Moderation/Panel/ConfirmActionDialog.vue | 79 ++
src/components/Moderation/Panel/LookupTab.vue | 276 +++++++
.../Moderation/Panel/ModeratorsTab.vue | 318 ++++++++
.../Moderation/Panel/ServerOwnerTab.vue | 160 ++++
src/composables/useBulkSelection.ts | 156 ++++
src/router/index.ts | 5 +
src/stores/moderation.ts | 33 +-
src/views/ModerationView.vue | 161 ++++
15 files changed, 1829 insertions(+), 781 deletions(-)
delete mode 100644 src/components/Moderation/AdminModerationPanel.vue
create mode 100644 src/components/Moderation/Panel/ActionBadge.vue
create mode 100644 src/components/Moderation/Panel/ActivityTab.vue
create mode 100644 src/components/Moderation/Panel/BulkSelectionTray.vue
create mode 100644 src/components/Moderation/Panel/ConfirmActionDialog.vue
create mode 100644 src/components/Moderation/Panel/LookupTab.vue
create mode 100644 src/components/Moderation/Panel/ModeratorsTab.vue
create mode 100644 src/components/Moderation/Panel/ServerOwnerTab.vue
create mode 100644 src/composables/useBulkSelection.ts
create mode 100644 src/views/ModerationView.vue
diff --git a/src/App.vue b/src/App.vue
index d548555..1b02572 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,13 +2,14 @@
import { RouterLink, RouterView } from 'vue-router'
import { onMounted } from 'vue'
import AuthButton from '@/components/AuthButton.vue'
-import AdminModerationPanel from '@/components/Moderation/AdminModerationPanel.vue'
import IconDiscord from '@/components/icons/IconDiscord.vue'
import IconYoutube from '@/components/icons/IconYoutube.vue'
import IconGithub from '@/components/icons/IconGithub.vue'
import { useAuth } from '@/stores/auth'
+import { useModerationStore } from '@/stores/moderation'
const { initAuth } = useAuth()
+const moderationStore = useModerationStore()
onMounted(async () => {
await initAuth()
@@ -31,6 +32,11 @@ onMounted(async () => {
Map Leaderboards
Player Lookup
Servers
+ Moderation
@@ -38,9 +44,6 @@ onMounted(async () => {
-
-
-
-
- Signed in as
{{ user.username }}
-
perms 0x{{ user.permissions.toString(16) }}
+
+
Signed in as {{ user.username }}
+
From 33de8bf238e5535603bba377fc509d5db82a1e8c Mon Sep 17 00:00:00 2001
From: tommy
Date: Fri, 8 May 2026 19:04:59 -0400
Subject: [PATCH 03/13] decode SERVER_OWNER_EDIT_SERVER and EDIT_SERVERS
permission bits
---
src/types/moderation.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/types/moderation.ts b/src/types/moderation.ts
index 0536101..35dc444 100644
--- a/src/types/moderation.ts
+++ b/src/types/moderation.ts
@@ -61,7 +61,9 @@ export class UserPermissions {
static readonly INVALIDATE_TIMES = 1 << 0;
static readonly BAN_PLAYERS = 1 << 1;
static readonly SERVER_OWNER_INVALIDATE_TIMES = 1 << 2;
+ static readonly SERVER_OWNER_EDIT_SERVER = 1 << 3;
static readonly UNDO_MOD_ACTION = 1 << 15;
+ static readonly EDIT_SERVERS = 1 << 28;
static readonly BYPASS_RATELIMITS = 1 << 29;
static readonly CREATE_KEY = 1 << 30;
static readonly MANAGE_KEYS = 1 << 31;
@@ -97,7 +99,9 @@ const KNOWN_FLAGS: Array<[number, PermissionLabel]> = [
[UserPermissions.INVALIDATE_TIMES, { name: 'Invalidate Times', tone: 'red' }],
[UserPermissions.BAN_PLAYERS, { name: 'Ban Players', tone: 'red' }],
[UserPermissions.SERVER_OWNER_INVALIDATE_TIMES,{ name: 'Server-Owner Invalidate', tone: 'purple' }],
+ [UserPermissions.SERVER_OWNER_EDIT_SERVER, { name: 'Server-Owner Edit', tone: 'purple' }],
[UserPermissions.UNDO_MOD_ACTION, { name: 'Reverse Mod Actions', tone: 'purple' }],
+ [UserPermissions.EDIT_SERVERS, { name: 'Edit Servers', tone: 'blue' }],
[UserPermissions.BYPASS_RATELIMITS, { name: 'Bypass Rate Limits', tone: 'yellow' }],
[UserPermissions.CREATE_KEY, { name: 'Create API Keys', tone: 'blue' }],
[UserPermissions.MANAGE_KEYS, { name: 'Manage API Keys', tone: 'blue' }],
From bad54cef1044ea423de3328e2107d05c38fdaf03 Mon Sep 17 00:00:00 2001
From: tommy
Date: Fri, 8 May 2026 19:35:44 -0400
Subject: [PATCH 04/13] npm audit fix
---
package-lock.json | 84 ++++++++++++++++++++++++++---------------------
1 file changed, 47 insertions(+), 37 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index c7c5886..64ed94b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -926,10 +926,11 @@
}
},
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -993,10 +994,11 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1256,10 +1258,11 @@
}
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -2560,10 +2563,11 @@
"dev": true
},
"node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -3054,10 +3058,11 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3314,10 +3319,11 @@
}
},
"node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/fs-extra": {
"version": "11.3.0",
@@ -3963,9 +3969,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
- "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
@@ -4378,10 +4384,11 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -4402,9 +4409,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.5",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
- "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [
{
"type": "opencollective",
@@ -4419,6 +4426,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -4813,9 +4821,10 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -4977,9 +4986,9 @@
"dev": true
},
"node_modules/vite": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
- "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
+ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
@@ -5157,9 +5166,10 @@
}
},
"node_modules/vite/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
From bad7103f111be17a1d29c97fe7a6b1707d846d83 Mon Sep 17 00:00:00 2001
From: tommy
Date: Sat, 9 May 2026 18:42:19 -0400
Subject: [PATCH 05/13] wire bulk selection through times lists; retire old
bulk modal
per-row right-click toggle and visual ring; page select/clear button
in the times list header; tray mounted globally so selections persist
across navigation between maps, players, and the moderation panel.
---
src/App.vue | 3 +
.../Moderation/BulkModerationModal.vue | 306 ------------------
src/components/TimeLists/TimesList.vue | 77 ++---
src/components/TimeLists/TimesListItem.vue | 39 ++-
src/views/ModerationView.vue | 2 -
5 files changed, 78 insertions(+), 349 deletions(-)
delete mode 100644 src/components/Moderation/BulkModerationModal.vue
diff --git a/src/App.vue b/src/App.vue
index 1b02572..7aedfb6 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,6 +2,7 @@
import { RouterLink, RouterView } from 'vue-router'
import { onMounted } from 'vue'
import AuthButton from '@/components/AuthButton.vue'
+import BulkSelectionTray from '@/components/Moderation/Panel/BulkSelectionTray.vue'
import IconDiscord from '@/components/icons/IconDiscord.vue'
import IconYoutube from '@/components/icons/IconYoutube.vue'
import IconGithub from '@/components/icons/IconGithub.vue'
@@ -44,6 +45,8 @@ onMounted(async () => {
+
+