> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shdevelopment.org/llms.txt
> Use this file to discover all available pages before exploring further.

# sh-admin: Server Administration Panel for RedM Servers

> sh-admin provides a RedM admin panel with Discord role permissions, ACE and framework fallbacks, reports, bans, whitelist, trust scores, maintenance tools, Discord logging, and developer utilities.

The `sh-admin` resource gives RedM staff a centralized administration toolkit through a NUI panel, hotkeys, and chat commands. It supports online player management, teleport tools, spectating, report handling, warns, kicks, bans, unbans, inventory grants, finance adjustments, job tools, offline player management, maintenance mode, Discord webhook logging, trust score enforcement, and developer tools.

Access is validated server-side for every sensitive action. The primary permission source is Discord role membership through the Discord API, with ACE permissions, framework groups, framework jobs, and command ACE as fallback sources.

<Warning>
  Keep Discord bot tokens and webhook URLs out of public docs, screenshots, and repositories. If a real token or webhook URL has already been shared, rotate it in Discord before using this resource in production.
</Warning>

## Requirements

* RedM server using `fx_version 'cerulean'` and `game 'rdr3'`
* `oxmysql`, started before `sh-admin`
* One supported framework:
  * VORP
  * RSG / RSG Core
* MySQL or MariaDB database access for the tables in `install/sql.sql`
* Discord bot token and guild ID if Discord role permissions, whitelist, or bot presence are enabled
* The folder/resource must be named exactly `sh-admin`

The resource declares `lua54 'yes'` and `node_version '22'`. The Node runtime is used by `server/discord_presence.js` when Discord bot live presence is enabled.

## Installation

<Steps>
  <Step title="Install dependencies">
    Ensure `oxmysql` and your framework resource are installed and working.
  </Step>

  <Step title="Add the resource">
    Copy the folder into your server resources directory and keep the folder name as `sh-admin`.
  </Step>

  <Step title="Import SQL">
    Run `install/sql.sql` against your database.
  </Step>

  <Step title="Configure server.cfg">
    Start dependencies first, then start `sh-admin`.

    ```cfg server.cfg theme={null}
    ensure oxmysql
    ensure vorp_core # or rsg-core / rsg-core-main
    ensure sh-admin
    ```
  </Step>

  <Step title="Configure permissions">
    Add Discord role IDs and/or ACE permissions before opening the panel on a live server.
  </Step>

  <Step title="Restart and verify">
    Restart the server, join with a permitted account, and open the panel with `/admin` or PageDown.
  </Step>
</Steps>

## Framework Support

Set framework detection in `config.lua`:

```lua config.lua theme={null}
Config.Framework = 'auto' -- 'auto', 'vorp', or 'rsg'
```

The framework bridge handles player identity, inventories, item catalogs, money, jobs, bank balances, drops, and coordinate lookups. Banking can be controlled separately:

```lua config.lua theme={null}
Config.Banking = {
    Mode = 'auto', -- 'auto', 'vorp', 'rsg', 'custom', or 'off'
    MaxAccounts = 15,
}
```

## Permissions

Permission checks run in this order:

1. Discord role permissions from `Config.DiscordRoles`
2. Action-specific ACE nodes from `Config.Permissions.Ace.Map`
3. Framework user groups and character groups from `Config.Permissions.UserGroups`
4. Framework job allowlist from `Config.Permissions.Jobs`
5. Final command ACE fallback such as `command`, `group.admin`, `rsgcore.admin`, or `rsgcore.god`

### Discord Role Permissions

Configure your bot token, guild ID, role buckets, and action mappings:

```lua config.lua theme={null}
Config.DiscordRoles = {
    Enabled = true,
    Api = {
        Enabled = true,
        BotToken = 'YOUR_DISCORD_BOT_TOKEN',
        GuildId = 'YOUR_DISCORD_GUILD_ID',
        CacheSeconds = 60,
    },
    AdminRoles = {
        superadmin = { '111111111111111111' },
        admin = { '222222222222222222' },
        mod = { '333333333333333333' },
    },
    ActionRoles = {
        ['menu.open'] = { 'mod', 'admin', 'superadmin' },
        ['player.kick'] = { 'admin', 'superadmin' },
        ['player.ban'] = { 'admin', 'superadmin' },
        ['player.warn'] = { 'mod', 'admin', 'superadmin' },
        ['reports.staff'] = { 'mod', 'admin', 'superadmin' },
        ['devtools.items'] = { 'admin', 'superadmin' },
        ['maintenance.lock'] = { 'admin', 'superadmin' },
    },
}
```

Run this diagnostic command from the server console or in-game as an admin:

```cfg theme={null}
shadmindiscorddiag
```

### ACE Fallback

Use ACE permissions when Discord role checks are disabled, unavailable, or not desired for a specific deployment:

```cfg server.cfg theme={null}
add_ace group.admin sh.admin.menu allow
add_ace group.admin sh.admin.ban allow
add_ace group.admin sh.admin.kick allow
add_ace group.admin sh.admin.warn allow
add_ace group.admin sh.admin.player.teleport allow
add_ace group.admin sh.admin.reports.staff allow
add_ace group.admin sh.admin.dev.items allow
add_ace group.admin sh.admin.maintenance.lock allow

add_principal identifier.license:YOUR_LICENSE_IDENTIFIER group.admin
```

<Warning>
  Never grant `sh.admin.menu`, `sh.admin.*`, or broad command ACE permissions to `group.everyone` or `builtin.everyone`.
</Warning>

### Common Action Keys

| Area         | Action keys                                                                                                                                                                                  |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Menu         | `menu.open`                                                                                                                                                                                  |
| Self tools   | `self.teleport`, `self.revive`, `self.invisible`, `self.god`, `self.noclip`, `self.ids`, `self.blips`                                                                                        |
| Player tools | `player.info`, `player.inventory`, `player.teleport`, `player.freeze`, `player.spectate`, `player.kick`, `player.warn`, `player.ban`, `player.unban`, `player.giveitem`, `player.giveweapon` |
| Reports      | `reports.submit`, `reports.staff`                                                                                                                                                            |
| Finances     | `finances.add`, `finances.remove`                                                                                                                                                            |
| Jobs         | `jobs.set`, `jobs.remove`                                                                                                                                                                    |
| Dev tools    | `devtools.items`, `devtools.coords`, `devtools.entities`, `devtools.clearzone`                                                                                                               |
| Offline      | `offline.search`, `offline.note`, `offline.ban`, `offline.finance`, `offline.queue`                                                                                                          |
| Maintenance  | `maintenance.view`, `maintenance.lock`, `maintenance.broadcast`                                                                                                                              |
| Punishments  | `punishments.templates`                                                                                                                                                                      |

## Commands

All public command names are configurable in `Config.Commands`.

| Default command | Config key       | Description                                    |
| --------------- | ---------------- | ---------------------------------------------- |
| `/admin`        | `Admin`          | Open the admin panel                           |
| `/report`       | `Report`         | Submit a player report or open the report form |
| `/requestadmin` | `RequestAdmin`   | Request staff assistance                       |
| `/unstuck`      | `Unstuck`        | Move yourself to nearby ground                 |
| `/myplaytime`   | `MyPlaytime`     | Show account playtime and trust score          |
| `/noclip`       | `NoClip`         | Toggle admin noclip                            |
| `/adminmode`    | `AdminMode`      | Toggle composite admin mode                    |
| `/tpm`          | `TeleportMarker` | Teleport to waypoint                           |
| `/invis`        | `Invisible`      | Toggle invisibility                            |
| `/kick`         | `Kick`           | Kick a player by server ID                     |
| `/warn`         | `Warn`           | Warn a player by server ID                     |
| `/ban`          | `Ban`            | Ban a player by server ID                      |
| `/goto`         | `GoTo`           | Teleport to a player                           |
| `/bring`        | `Bring`          | Bring a player to you                          |

Command examples:

```cfg theme={null}
/report bug Menu issue | The player list does not refresh
/requestadmin Need help | I am stuck under the map
/kick 12 Staff decision
/warn 12 FailRP
/ban 12 3 RDM in Valentine
/ban 12 Cheating
/goto 12
/bring 12
```

<Note>
  The report and request commands use a pipe character (`|`) between the short title and the full message when arguments are supplied. With no arguments, they open the compact NUI report form when `Config.Reports.OpenPromptOnCommand` is enabled.
</Note>

## Hotkeys

```lua config.lua theme={null}
Config.Keys = {
    OpenMenu = {
        Enabled = true,
        Control = 0x3C3DD371, -- PageDown
        CooldownMs = 450,
    },
    NoClip = {
        Enabled = true,
        Control = 0x1F6D95E5, -- F4
        CooldownMs = 250,
    },
    SpectateExit = {
        Enabled = true,
        Control = 0xB2F377E8, -- F
        Label = 'F',
    },
}
```

The client also registers internal commands `sh_admin_open` and `sh_admin_noclip`; those forward to server-side permission checks.

## Admin Panel Tabs

<Accordion title="Dashboard">
  Shows online player count, report counts by status, selected player summary, recent reports, and quick buttons for Players, Reports, and data refresh.
</Accordion>

<Accordion title="Self">
  Includes teleport to marker, self revive, noclip, invisibility, god mode, player IDs, player blips, self item or weapon grants, and self finance tools.
</Accordion>

<Accordion title="Players">
  Shows live player list data including source ID, name, job, and ping. Staff can view profile/history, open inventory, goto, bring, spectate, freeze/unfreeze, grant items or weapons, adjust money, set or remove jobs, warn, kick, and ban.
</Accordion>

<Accordion title="Reports">
  Players can submit reports. Staff can filter reports, view nearby player snapshots, claim or unclaim, add staff notes, reply to the reporter, change status, delete reports, goto/bring the reporter, and teleport back after assisting.
</Accordion>

<Accordion title="Dev Tools">
  Includes coordinate copy formats, item database editor, item catalog loading, horse and wagon spawning, nearest horse/wagon deletion, zone clearing, and entity lookup by network ID.
</Accordion>

<Accordion title="Punishments">
  Provides configurable Ban QuickSets, Reason Catalog auto-duration matching, active ban listing, and unban tools.
</Accordion>

<Accordion title="Offline">
  Searches indexed offline players by name, character ID, or identifier. Staff can add notes, queue finance changes, force queue sync if the player is online, and apply offline bans.
</Accordion>

<Accordion title="Operations">
  Controls maintenance join lock and full-screen staff broadcasts. Recent broadcast history is stored and shown in the panel.
</Accordion>

## Reports

Report behavior is configured in `Config.Reports`:

```lua config.lua theme={null}
Config.Reports = {
    Enabled = true,
    NearbyDistance = 50.0,
    AllowReportCommand = true,
    OpenPromptOnCommand = true,
    AssistPedModel = 'cs_crackpotrobot',
    RestoreCharacterCommand = 'rc',
    DefaultStatus = 'open',
    AllowedStatuses = {
        open = true,
        claimed = true,
        resolved = true,
        closed = true,
    },
    AllowedCategories = {
        bug = true,
        player = true,
        question = true,
    },
}
```

When staff use report goto/bring flows, the script can temporarily change the admin model and save a return point. Set `AssistPedModel = nil` to disable the temporary model. Set `RestoreCharacterCommand = nil` or `false` if your framework should not run a restore command.

## Bans And Punishments

`/ban` and the panel support temporary bans and permanent bans. A ban duration of `0` means permanent. Active bans are checked during `playerConnecting`.

```lua config.lua theme={null}
Config.Punishments = {
    BanQuickSets = {
        { key = 'toxicity_1d', label = 'Toxicity (1 Day)', reason = 'Toxic behavior / staff disrespect', durationDays = 1 },
        { key = 'rdm_3d', label = 'RDM (3 Days)', reason = 'Random deathmatch (RDM)', durationDays = 3 },
        { key = 'cheating_perm', label = 'Cheating (Permanent)', reason = 'Cheating / unauthorized software', durationDays = 0 },
    },
    ReasonCatalog = {
        { key = 'rdm', reason = 'Random deathmatch (RDM)', durationDays = 3 },
        { key = 'cheating', reason = 'Cheating / unauthorized software', durationDays = 0 },
    },
    AutoDuration = {
        Enabled = true,
        MatchMode = 'exact_or_prefix',
    },
}
```

If staff type a reason matching the reason catalog and do not provide an explicit duration, sh-admin applies the configured duration.

## Trust Score And Playtime

sh-admin tracks account-wide playtime and trust score using `sh_admin_trustscore`.

```lua config.lua theme={null}
Config.TrustScore = {
    Enabled = true,
    BaseScore = 80,
    MinScore = 0,
    MaxScore = 100,
    Gain = {
        IntervalSeconds = 7200,
        AmountPerInterval = 2,
    },
    Infractions = {
        warn = { Loss = 10 },
        kick = { Loss = 15 },
        tempban = { Loss = 35 },
        permban = { Loss = 90 },
    },
    AutoBan = {
        Enabled = true,
        Threshold = 30,
        DurationDays = 7,
        Reason = 'Automatic trustscore enforcement',
    },
}
```

Scores are clamped to the configured min and max. When trust falls strictly below the auto-ban threshold, the player is automatically banned for the configured duration. Players can view their own playtime and score with `/myplaytime`.

## Whitelist

The join whitelist uses Discord role IDs:

```lua config.lua theme={null}
Config.Whitelist = {
    Enabled = true,
    FailClosed = true,
    RequiredRoleIds = {
        '111111111111111111',
    },
    BypassAce = {
        'sh.admin.whitelist.bypass',
    },
}
```

If `FailClosed` is true, players are denied when Discord API checks are unavailable. Staff can bypass with any configured maintenance or whitelist bypass ACE.

## Maintenance Mode

Maintenance mode blocks new joins while allowing configured ACE bypasses:

```lua config.lua theme={null}
Config.Maintenance = {
    Enabled = true,
    BroadcastDurationMs = 12000,
    BroadcastShowAuthor = false,
    BroadcastAuthorMode = 'staff',
    BroadcastHistoryLimit = 25,
    BroadcastUseChatFallback = false,
    BroadcastUseNotifyFallback = false,
    CustomJoinMessage = '',
    BypassAce = {
        'sh.admin.maintenance.bypass',
    },
}
```

Maintenance state and broadcast history are stored in SQL, so lock state survives resource restarts.

## Discord Webhooks

Admin actions, reports, bans, finances, jobs, devtools actions, and security events can log to Discord webhooks.

```lua config.lua theme={null}
Config.Webhooks = {
    Enabled = true,
    Username = 'sh-admin',
    AvatarURL = '',
    Footer = 'sh-admin',
    Default = 'YOUR_DEFAULT_WEBHOOK_URL',
    MentionRoleId = '',
    Channels = {
        admin_actions = '',
        reports = '',
        bans = '',
        finances = '',
        jobs = '',
        devtools = '',
        security = '',
    },
    AntiSpam = {
        WindowSeconds = 4,
        MaxEvents = 6,
    },
}
```

Empty channel URLs fall back to `Default`.

## Discord Bot Presence

The optional Discord bot presence updater connects to the Discord Gateway and updates the bot activity text.

```lua config.lua theme={null}
Config.DiscordBotStatus = {
    Enabled = true,
    Api = {
        BotToken = '',
    },
    ServerName = 'RedM Server',
    Status = 'online', -- online, idle, dnd, invisible
    ActivityType = 'playing', -- playing, streaming, listening, watching, competing
    ActivityText = '{server} | {players}/{max} players for {uptime}',
    StreamingUrl = '',
    UpdateSeconds = 60,
}
```

Supported placeholders are `{server}`, `{players}`, `{max}`, `{uptime}`, `{uptime_seconds}`, `{uptime_days}`, `{uptime_hours}`, and `{uptime_minutes}`.

## Item And Weapon Tools

The player and self grant pickers use framework catalogs plus the `sh_admin_items` item editor table. Catalog size can be tuned:

```lua config.lua theme={null}
Config.Catalogs = {
    MaxItems = 12000,
    MaxWeapons = 1200,
}
```

The item editor can write VORP items directly into the framework `items` table when running VORP. For RSG, it stores editor rows in `sh_admin_items` and prints an RSG item snippet to the server console.

## Database Tables

The install SQL creates these tables:

| Table                            | Purpose                                          |
| -------------------------------- | ------------------------------------------------ |
| `sh_admin_reports`               | Report/ticket records                            |
| `sh_admin_report_notes`          | Staff notes and replies                          |
| `sh_admin_report_nearby_players` | Nearby player snapshot captured on report submit |
| `sh_admin_bans`                  | Active and historical ban records                |
| `sh_admin_ban_history`           | Ban revoke/history actions                       |
| `sh_admin_trustscore`            | Trust score and playtime                         |
| `sh_admin_warns`                 | Warn history                                     |
| `sh_admin_audit_log`             | Server-side action audit log                     |
| `sh_admin_job_labels`            | Custom job labels set by staff                   |
| `sh_admin_items`                 | Admin item editor/catalog rows                   |
| `sh_admin_player_index`          | Offline player search index                      |
| `sh_admin_offline_notes`         | Offline staff notes                              |
| `sh_admin_offline_queue`         | Queued offline finance actions                   |
| `sh_admin_maintenance_state`     | Maintenance join lock state                      |
| `sh_admin_broadcast_history`     | Staff broadcast history                          |

The script also creates or migrates some item editor columns at runtime.

## Exports

Other resources can check admin access or frozen state:

```lua theme={null}
local hasAccess = exports['sh-admin']:HasAdminAccess(source, 'menu.open')
local frozen = exports['sh-admin']:IsPlayerFrozenByAdmin(source)
```

`HasAdminAccess` accepts any sh-admin action key and runs the same server-side permission logic.

## Localization

Set the active locale in `config.lua`:

```lua config.lua theme={null}
Config.Locale = 'en' -- en, es, or de
```

Language files are loaded from `locales/*.lua` and can be edited because they are included in `escrow_ignore`.

## Troubleshooting

| Issue                                               | Check                                                                                                                                  |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Admin menu does not open                            | Confirm the resource is named `sh-admin`, `menu.open` permission passes, and `/shadmindiscorddiag` has no Discord configuration errors |
| Discord role checks fail                            | Verify the bot token, guild ID, bot membership in the guild, role IDs, and `CacheSeconds`                                              |
| Players are denied on join                          | Check whitelist role IDs, `FailClosed`, active bans, and maintenance join lock                                                         |
| SQL errors on start                                 | Import `install/sql.sql`, ensure `oxmysql` starts first, and verify the configured database                                            |
| Item grants are empty                               | Increase `Config.Catalogs.MaxItems`, confirm framework item tables exist, and check item editor rows                                   |
| RSG item editor does not add directly to core items | Copy the printed server console snippet into your RSG shared item config                                                               |
| Report assist breaks character restore              | Disable or change `Config.Reports.RestoreCharacterCommand` for your framework                                                          |
| No Discord bot presence                             | Enable `Config.DiscordBotStatus`, provide a valid bot token, and check server console logs from `sh-admin][discord-presence`           |
