feat(adminmenu): add a modular, hardened admin menu - #117
Closed
ASTROWwwW wants to merge 18 commits into
Closed
Conversation
Adds AdminLimits caps (props bytes, page sizes, plate attempts, offline search) and AdminMenu tuning (model timeout, blip, spectate, troll, waypoint, server-data interval) consumed by the server and client refactors.
Introduces safeQuery/safeScalar/safeUpdate, a registerCallback wrapper that always answers exactly once, and a getActionPermission accessor. Routes read paths through the safe wrappers, self-heals the impounds cache, derives translations from the locale table, and replaces recursive plate generation with a loop.
getActiveBans and BanCache.get no longer mutate the cache on read; a shared prune runs on expiry change and a maintenance timer. initDB steps are isolated so ban loading always runs, and the playerConnecting ban check is wrapped so a lookup failure resolves the deferral instead of hanging the join.
PlayerAction and ServerManagement become permission-gated handler registries with a single config-sourced gate; all server callbacks go through the registerCallback wrapper; xPlayer lookups use ESX.GetPlayerFromId (portable on stock ESX); and the helper copy-commands share one factory.
Adds onResourceStop, spectate-failure and respawn state restoration; collapses the NUI server bridges, quick toggles and vehicle cycles behind factories; dedupes trim and model loading; and makes client_scripts an explicit ordered list (main.lua renamed to init.lua).
README gains a FeaturePermissions catalog for operators building granular groups; en.lua gains server-facing error keys.
Bans now capture every enabled identifier (license, discord, steam, ... per Config.Ban.Identifiers) and the ban cache indexes each, so a banned player is matched on reconnect via ANY identifier instead of license only. Adds an identifiers column with migration and a connect check over all of the player's identifiers; legacy license-only bans still match. Also caches the sorted active-ban view (rebuilt only on cache change via a generation counter) so pagination no longer re-sorts every request, and anchors the vehicle search to a prefix so its index can be used.
Replaces the SUBSTRING_INDEX filter in the offline player search with an indexable IN (license, license2) lookup so it no longer forces a full users table scan.
ESX is obtained through exports["es_extended"]:getSharedObject(), so xPlayer methods cross a resource boundary and arrive as function references: tables carrying a __call metamethod. type(x) == "function" is therefore always false for them, and the guards using it rejected every call. giveMoney, takeMoney and cleanInventory could never succeed, reporting that the ESX build did not expose a method it did expose. Helpers.isCallable accepts a function or a callable table. It reimplements what ESX.IsFunctionReference does so the resource keeps working on builds predating that helper.
The uptime was formatted as %02d:%02d:%02d over days, hours and minutes, which reads as a HH:MM:SS clock. Eight minutes of uptime displayed as 00:00:08 and looked like eight seconds standing still. Units are now explicit: 42s, 8m, 2h 15m, 3d 04h.
The offline search only accepted a complete license: or charN: identifier, and its results were dropped anyway: the callback never returned a success field, which the UI checks before rendering. Searching had therefore never displayed anything. Searching now matches partial identifiers, first name, last name, full name and phone number, and merges connected players into the same result set, deduplicated by identifier. Group, phone number and first-seen date are returned as well. A complete identifier still takes the indexed path, so the common precise lookup does not degrade into a table scan. LIKE metacharacters are escaped, otherwise a single % would return every user in one request.
Records who did what to whom, in a new admin_logs table. Only actions are recorded, never reads: the menu polls player and vehicle lists constantly and would otherwise drown the log. Permission denials are recorded too, since a blocked attempt is the more interesting signal. Entries are queued in memory and written in batches by a background thread, so an admin action never waits on the database. The queue is bounded and sheds its oldest entries rather than growing without limit, every write goes through the safe query helper, and a flush on resource stop keeps the last window. Payloads are size-capped, and a payload replaced by a truncation marker stays valid JSON. Network identity and credential-shaped keys are dropped before storage. Records are rate-limited per source: server callbacks are reachable by any client, so denials could otherwise be spammed until real entries were evicted. Retention defaults to 30 days, purged hourly in bounded chunks so the table is never locked for long. An optional Discord webhook accepts https only and forwards an allowlist of actions. Replaces the kicks table, which was created and written to but never read back by anything. Any existing table is left in place: a resource dropping tables on start is a footgun. Adds logAdminAction and getAdminLogs exports, the resource having exposed none until now, and a logViewer permission separate from admin.
Recent Players was fed only by playerDropped, so it listed players who had disconnected since the resource started and stayed empty the rest of the time. Any successful action on a connected player now adds them straight away. The entry keeps their server id and an online status so the UI can act on it live; playerDropped later rewrites the same identifier as offline, and deduplication by identifier prevents a duplicate. The snapshot is taken after the handler runs, so an entry reflects the state the action produced rather than the one preceding it.
An offline ban stores only the license, which looks like an oversight next to the online path capturing every identifier. It is deliberate: the player is not connected, so their discord and steam identifiers cannot be read, and ESX does not persist them.
A new sidebar tab renders the log as an activity feed rather than a table. Each action has its own sentence built from its payload, so a row reads "Astrxw gave $5.000 to Tester1 (bank)" instead of showing raw JSON. A colour marker and chip carry the category, timestamps are relative, and a details button reveals the exact time, identifiers and every payload field. Search runs on the server across admin, target and action, so it reaches the whole retained history rather than only the loaded page. It is debounced, needs two characters, and escapes LIKE metacharacters. Category and period filters sit alongside it, and the scroller pulls the next page in as it nears the bottom. Rows expand on demand, so a virtual list would mis-measure them: a plain overflow scroller is used instead. The bundle is rebuilt from these sources; the tab folder is admin-logs rather than logs because the Vite template gitignores a bare logs path, which would have dropped the sources from the repository.
The init payload no longer carries the translation table. The client reads the active locale it already ships and the NUI merges it over its English defaults, so a partial locale keeps every key.
N0tNvll
approved these changes
Aug 4, 2026
Contributor
|
Some parts needs to be fixed i will merge and fix by myself |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
esx_adminmenu, building on @zoxile's #111. The original menu is the firstcommit; the rest refactor it for modularity, harden it, and add features. Each
commit is scoped to one concern for step-by-step review.
Changes
if/elseifchains, one permission gate per namespacetype(x) == "function"(fixesgiveMoney,takeMoney,cleanInventory)logAdminAction/getAdminLogsexports, optional webhook. Replaces the unusedkickstableTested locally (automated suite + in-game), compatible with es_extended 1.14.
PR Checklist