Skip to content

YourBOX Admin Panel

The 2020 admin surface lives under YourBOX/src/accounts/admin/post/ with three PHP scripts (index.php, edit.php, delete.php) that render a Bootstrap table and allow editing or deleting videos. There is no user management, no comment moderation, no report queue and no audit trail.

The new admin panel is built with EasyAdmin and lives under /admin. It is gated by the ROLE_ADMIN role from the authentication model and exposes four sections: catalog, users, moderation and audit.

flowchart TB
Dashboard[Admin dashboard]
Dashboard --> Catalog[Catalog: Videos, Tags, Categories]
Dashboard --> Users[Users: Accounts, Roles, Sessions]
Dashboard --> Moderation[Moderation: Reports, Comments, Actions]
Dashboard --> Audit[Audit: Read only log of privileged actions]
Catalog --> VideoCrud[Video CRUD: title, tags, status, transcoding state]
Users --> UserCrud[User CRUD: search, suspend, role change]
Moderation --> ReportCrud[Report queue: pending, acknowledged, resolved]
Moderation --> CommentMod[Comment moderation: soft delete, restore]
Audit --> AuditList[Audit list: filter by actor, target, action]

Each EasyAdmin CRUD class lives under the matching bounded context:

  • App\Catalog\UserInterface\Admin\VideoCrudController
  • App\Catalog\UserInterface\Admin\CategoryCrudController
  • App\Identity\UserInterface\Admin\UserCrudController
  • App\Moderation\UserInterface\Admin\ReportCrudController
  • App\Moderation\UserInterface\Admin\CommentModerationCrudController
  • App\Moderation\UserInterface\Admin\AuditLogCrudController

The video CRUD lists every video with filterable columns for status (pending, published, flagged, removed), uploader username, upload date, view count, like count and tag set. The edit form exposes the title, description, tag list, main category and a manual override for the status. The form never touches the storage keys directly; key management happens server side through the Catalog context.

The list view links each row to the public playback page and to a dry run preview that uses the same Twig template but rendered with the moderator context, so the moderator sees the same playback experience the public sees.

A button on the row triggers a RetryTranscoding command for videos in the failed status. The handler dispatches a new TranscodeVideo message and updates the status.

The user CRUD lists every user with columns for username, email, role list, oidc_sub presence, last seen timestamp and account age. Filters include the role, the oidc_sub presence (to separate OIDC users from local fallback accounts), and the suspension state.

The role change form is gated by an extra confirmation step that captures a free text reason. The reason is written to the audit log alongside the privilege change.

A “view sessions” action lists active session ids for the user and exposes a “revoke all sessions” button. The Symfony session storage is Redis, so invalidating a session is a single Redis DEL for the corresponding key prefix.

The report queue lists every open Report aggregate with the target (video, comment, user), the reporter username, the reason category and the elapsed time since the report was filed. Sorting defaults to oldest first to keep the queue moving.

Opening a report shows the targeted content next to the report payload. The moderator selects one of three actions: dismiss, acknowledge with a soft delete, or escalate to admin. Each action dispatches a command in the Moderation context and records an audit log entry.

The comment moderation view exposes a flat list of comments with a search by username and a date range filter. Soft deletion replaces the rendered content with a tombstone while preserving the thread shape and the foreign keys.

The audit log is read only. The CRUD class disables every action except index and detail. The list view filters by actor (subject or username), action type, target type and date range. The payload column shows the JSONB body, prettified.

The audit log retention policy is documented on the Observability page: 90 days hot in PostgreSQL, then archived to OpenSearch via Logstash for long term cold storage.

EasyAdmin is mounted under /admin and the route is gated by the access control rule in security.yaml:

access_control:
- { path: ^/admin, roles: ROLE_ADMIN }

A non admin user reaching /admin is redirected to /oidc/login if not authenticated, or returns a 403 if authenticated. The 403 page documents the contact path to request admin access.

Inside the admin panel, finer permissions are enforced by Symfony voters:

  • ROLE_MODERATOR can use the moderation dashboard but cannot promote users or delete videos.
  • ROLE_ADMIN has full access.
  • The audit log is visible to both roles but only ROLE_ADMIN can export it.

Every state changing action in the admin panel passes through Symfony Forms with CSRF protection enabled. EasyAdmin generates the tokens automatically. Bulk actions (soft delete N comments, suspend N users) are batched through Symfony Messenger so that a long running operation does not hold an HTTP request.

The admin panel uses the Tailwind tokens from the Frontend page applied to EasyAdmin’s Twig override slots. The look matches the public site: same primary color, same typography, same spacing scale. EasyAdmin’s default Bootstrap theme is replaced by Tailwind component classes from app/assets/styles/admin.css.