# GCC Admin Panel

A modern, self-contained replacement for the legacy `edmin/` admin panel.

It is a **single-page app** (vanilla ES modules, no build step) talking to a
**JSON API** written in script-based CFML (Lucee). Authentication, the audit
trail, access-control overrides, and admin accounts live in their own database,
**`gcc_admin`** — the game databases (`gcc`, `gcc_log`) are only ever read and
written for actual game data.

```
Admin/
├── index.cfm               SPA shell (serves the HTML page + cache-bust key)
├── Application.cfc         Own CFML application: sessions + datasources
├── api/
│   ├── index.cfm           API router  (/Admin/api/?action=module.method)
│   └── components/         One script CFC per module
│       ├── Base.cfc          shared: queries, auth, ACL, audit, game helpers
│       ├── Auth.cfc          login / logout / first-run bootstrap
│       ├── Dashboard.cfc     overview metrics
│       ├── Users.cfc         player search / detail / every editor tab
│       ├── UserActions.cfc   moderation actions (port of s_admin_action)
│       ├── Chat.cfc          live chat feed: report / warn / silence / remove
│       ├── Moderation.cfc    chat + PM queues, IP / email blacklists
│       ├── AntiCheat.cfc     shared-IP / cookie / turn-burn detection
│       ├── Economy.cfc       money-supply reporting
│       ├── Federations.cfc   fed management
│       ├── Content.cfc       game-data reference + editors
│       ├── Events.cfc        global event watch
│       ├── Admins.cfc        panel admin account management
│       ├── Audit.cfc         audit-log search
│       ├── ServerOps.cfc     runtime refresh, panel settings, announcement banner
│       └── Acl.cfc           access-control screen backend
├── assets/
│   ├── admin.css           design system (dark, self-contained)
│   ├── app.js              client core: router, API client, UI kit
│   ├── icons.js            inline SVG icon set
│   └── views/*.js          one module per screen (lazy-loaded per route)
├── sql/
│   ├── gcc_admin.sql       panel schema (run this first)
│   ├── chat_post_expand.sql  widens gcc.chat.post — runs on the GAME db
│   └── admin_reset.sql     force-relog flag table — runs on the GAME db
├── docs/                   reference documentation (start here)
└── Skills/                 task playbooks for working on the panel
```

## Documentation

| Doc | Read it when |
|---|---|
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | You need the request lifecycle, router contract, or database layout |
| [`docs/ACCESS-CONTROL.md`](docs/ACCESS-CONTROL.md) | Anything about clearance — includes the full section registry |
| [`docs/API-REFERENCE.md`](docs/API-REFERENCE.md) | You need an endpoint and the ACL guarding it |
| [`docs/AUDIT-LOGGING.md`](docs/AUDIT-LOGGING.md) | You're adding an action or a view that should be recorded |
| [`docs/CHAT-MODERATION.md`](docs/CHAT-MODERATION.md) | You're touching chat, complaints, or post removal |
| [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md) | **Before writing code** — CFML/JS/CSS house style and traps |
| [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) | Local setup, verifying a change, schema migrations |

`Skills/` holds step-by-step playbooks for the common jobs (add an endpoint, add
a view, add an ACL section, add audit logging, verify a change, work on chat).

## Setup

### 1. Create the `gcc_admin` database

Run the schema once as a MySQL user that can `CREATE`:

```bash
mysql -u root -p < app/Admin/sql/gcc_admin.sql
```

It creates the `gcc_admin` database and six tables:

| Table              | Purpose                                                       |
|--------------------|---------------------------------------------------------------|
| `admin_account`    | Panel logins. Argon2id password hashes only — no plaintext.   |
| `admin_login_log`  | Every login attempt (also drives per-IP flood lockout).        |
| `admin_audit_log`  | Full trail of every mutating action **and view**.              |
| `admin_note`       | Free-form case notes pinned to a player.                       |
| `admin_setting`    | Panel key/value settings (MOTD, whether to email players).     |
| `admin_acl`        | Per-section clearance overrides (Access Control screen).        |

The application connects as the same `WolfrenInd` MySQL account the game uses.
If that account doesn't already have rights on `gcc_admin`, uncomment the
`GRANT` at the bottom of the SQL file and run it.

> The `gcc_admin` datasource is already declared in the game's root
> `Application.cfc`, and the panel declares its own copy in
> `Admin/Application.cfc`, so no Lucee admin datasource setup is needed.

### 2. Apply the game-database migrations

Two files in `sql/` target the **game** database (`gcc`), not `gcc_admin`. Both
declare `USE gcc;` at the top and are safe to re-run.

```bash
mysql -u root -p < app/Admin/sql/chat_post_expand.sql
mysql -u root -p < app/Admin/sql/admin_reset.sql        # optional, see below
```

- **`chat_post_expand.sql`** widens `gcc.chat.post` to `varchar(255)` so an
  admin-scrubbed chat post can hold a full-length replacement plus its styling
  wrapper.
- **`admin_reset.sql`** creates `gcc.admin_reset`, the **forced-logout** table
  (below). The panel auto-creates it on first use, so running this by hand is
  optional — provided for reference, or if the panel's DB user lacks `CREATE`.

#### The forced-logout system (`gcc.admin_reset`)

When an admin edits a player's account — resources, infrastructure, artifacts,
fleet, colonies, projects, research, empire name — or suspends/blacklists them,
the panel inserts a row here. On that player's next page load the game (the
reset block at the top of `i_f_800.cfm`) clears their session and cached data,
shows them the reason via `z_admin_reset_notice.cfm`, and deletes the row.

That is what stops a player continuing on a stale session with pre-edit state,
and it is a **shared DB flag** rather than in-memory state so it survives app
restarts and works across servers.

| Column | Purpose |
|---|---|
| `userid` | Player to reset (primary key — one pending reset per player) |
| `newname` | New login name when the edit was a rename |
| `kind` | `''`, `'suspend'`, or `'blacklist'` — drives the notice shown |
| `reason` | Admin's reason, displayed to the player on suspend/blacklist |
| `created_at` | When the flag was raised |

The game side reads it inside a `try/catch` and ignores failures, so a missing
table or transient DB issue can never break the game.

### 3. Create the first admin account

There is **no seed password in the repo**. When `admin_account` is empty, the
login screen shows a one-time **"Create first admin"** form that mints a level-9
superadmin and then disables itself. Just open the panel and fill it in.

### 4. Open the panel

```
<your-host>/Admin/
```

Locally that is `http://localhost:8888/Admin/` (or, from a git worktree, the
worktree's path prefix + `/Admin/` — see `docs/DEVELOPMENT.md`).

### Migrating existing edmin admins (optional)

`sql/gcc_admin.sql` contains a commented-out `INSERT ... SELECT` that copies the
legacy `gcc.admin` accounts in **inactive** and **without** passwords (legacy
plaintext passwords are intentionally not carried over). A level-5+ admin then
activates each one and generates a fresh password from the **Admins** screen.

## Access control

Clearance is the legacy 0–9 ladder, but *which* level each area needs is
**configurable at runtime**. The **Access Control** screen (level 9) sets a
minimum level per "section" — nav areas, per-field view/edit rights, individual
player actions, and tiered basic/full views. Overrides live in
`gcc_admin.admin_acl` and take effect within ~60 seconds; an empty table means
every section uses its built-in default. The Access Control section itself is
pinned to level 9 so no one can lock everyone out.

Levels are named in the UI:

| Level | Name | Level | Name |
|---|---|---|---|
| 0 | Guide | 5 | Junior Administrator |
| 1 | Senior Guide | 6 | Administrator |
| 2 | Moderator | 7 | Lead Administrator |
| 3 | Senior Moderator | 8 | Head Administrator |
| 4 | Lead Moderator | 9 | Super Administrator |

The full registry — every section, its default, and what it controls — is in
[`docs/ACCESS-CONTROL.md`](docs/ACCESS-CONTROL.md).

**Enforcement is server-side per method.** The sidebar, tabs, and buttons hide
what the signed-in admin can't reach, but that gating is cosmetic; the API
re-checks every call.

## How it talks to the game

Everything the legacy panel did to the game is preserved so the two stay
coherent and the in-game records players rely on don't change:

- **Moderation actions** write the legacy record on the player
  (`gcc.user_pm_abuse`), send the in-game system PM (`gcc.user_pm`), and, where
  the old panel emailed, still email — gated by the `send_player_emails` setting.
- **Silence** sets the same server-scope mute flag (`server.gc_mute<uid>`) the
  game chat (`i_chatb.cfm`) reads.
- **Chat removal** rewrites `gcc.chat.post` in place, wrapped in a
  `<span class="removed">` the game's own stylesheets render — see
  [`docs/CHAT-MODERATION.md`](docs/CHAT-MODERATION.md).
- **Account edits** force the player to re-log by raising a flag in
  `gcc.admin_reset`, so a suspended or edited account can't keep playing on a
  stale session (see *The forced-logout system* above).
- **Runtime refresh** re-runs the game's `s_loadvar.cfm` / `s_loadsystem.cfm`.
- Every action *also* writes the structured `admin_audit_log`, and the **Audit**
  screen reads both the new trail and the legacy game-side stream.

## SQL migrations

Everything in `sql/` **starts with a `USE`** (after its header comment, before
the first statement) or fully qualifies every object it touches. `USE` is
preferred — one line at the top instead of a prefix per statement.

This folder holds migrations for **three databases**: `gcc_admin`, `gcc` and
`gcc_log`. The folder's name biases you toward `gcc_admin`, and ten of the
fifteen files target `gcc`. A migration run against the wrong database is a
silent no-op, or creates a table somewhere nothing will ever read it.

With the `USE` in place, no database argument is needed:

```bash
mysql -u WolfrenInd -p < app/Admin/sql/forum_schema.sql
```

Full file-by-file table in
[`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md#every-migration-names-its-database--no-exceptions).

## Security notes

- **Passwords** (admin and player) are stored as Argon2id hashes via the shared
  `Modules/Functions/PasswordCrypto.cfm`. Admin accounts have no plaintext
  column at all; player password resets clear the legacy plaintext column and
  write only the hash (the game's `p_login.cfm` verifies the hash first).
- **Session + CSRF**: cookie session (HttpOnly, SameSite=Lax); every mutating
  request must send the `X-CSRF-Token` header issued at login.
- **SQL**: all dynamic values are bound as named query parameters. The few
  inlined values are server-owned constants or values reduced to integers /
  allow-listed identifiers (sort columns, resource column names, id lists
  filtered to digits).
- **XSS**: player-controlled text is rendered as DOM text nodes. Game-authored
  event HTML passes through an allowlist sanitizer that drops unknown tags,
  `on*` handlers, inline styles, and `javascript:`/`data:` URLs.
- **Login flood control**: 10 failed attempts from one IP in 15 minutes locks
  that IP out for 15 minutes.
- **Endpoint allowlist**: only methods named in a CFC's `this.endpoints` can be
  invoked over HTTP, so inherited helpers are unreachable.

## API shape

`GET|POST /Admin/api/?action=<module>.<method>`

- `GET` = reads, `POST` = mutations (JSON body + `X-CSRF-Token`).
- Responses are always `{ "ok": true, "data": … }` or
  `{ "ok": false, "error": … }`.

Full endpoint list in [`docs/API-REFERENCE.md`](docs/API-REFERENCE.md).

## Relationship to `edmin/`

This panel is additive and independent — the old `edmin/` directory is left
untouched. Once this is validated in production, `edmin/` can be retired.
Feature coverage carried over: user search/detail/edit, the full moderation
action set, chat & PM complaint queues, IP/email blacklists, multi-account
detection, fed management, economy totals, admin management, admin logs, event
watch, and the `*_ids.txt` reference data (now the live **Game Data** screen).

> Legacy `edmin/` is defunct and is **never** documented — here or in either
> changelog.
