Skip to content

Admin Panel

P7CO edited this page Jul 20, 2026 · 54 revisions

Admin Panel

apps/admin (package cms-admin): one FastAPI process serving the API and the server-rendered UI — Jinja templates on AdminLTE 4 (MIT, vendored verbatim with its license; Bootstrap 5 bundled), implemented 1:1 as its reference pages do — Source Sans 3 (OFL), Bootstrap Icons (MIT) and the theme's own behavior scripts vendored locally: collapsible sidebar, fullscreen toggle, light/dark/auto color mode (WCAG-gated in both palettes in CI), the canonical user menu. No CDN, no inline scripts, no build step. It drives cms-core, cms-validation and cms-build through their public APIs and never bypasses them.

Try it without installing anything: the demo site ships a read-only static snapshot at https://sardine.ph7x.com/admin/ — the real admin's pages, captured with demo content; nothing there saves, by construction.

Run it

pip install -e apps/admin
cms admin create-user editor-in-chief --role admin   # prompts for a password
SARDINE_STORAGE_URL="sqlite:///content.sqlite3" \
SARDINE_ADMIN_COOKIE_SECURE=0 \
  uvicorn --factory cms_admin.app:create_app

Sign in at /login. There are no default credentials — the first account always comes from the CLI. Passwords contain 12–1024 characters. Replacing an account through the CLI revokes all of its existing sessions.

Configuration (environment only — no config files with secrets)

Variable Default Meaning
SARDINE_STORAGE_URL sqlite:///content.db Storage URL (create_storage)
SARDINE_ADMIN_SESSION_HOURS 12 Session lifetime
SARDINE_ADMIN_COOKIE_SECURE 1 Set 0 only for plain-http local development
SARDINE_MEDIA_DIR media Media upload directory (the project's media/)
SARDINE_ADMIN_UPLOAD_MAX_MB 10 Upload size limit in MB
SARDINE_ADMIN_UPLOAD_MAX_PIXELS 40000000 Maximum decoded image pixel count
SARDINE_PROJECT_DIR . Project directory holding sardine.toml (panel builds)
SARDINE_ADMIN_PUBLISH_GATE 1 Set 0 to allow publishing despite validation errors

Accounts and security

  • Roles (least-privilege ladder): editor < reviewer < publisher < admin, enforced server-side.
  • Passwords: argon2id hashes; sessions server-side with only the token digest stored; production cookies use the __Host- prefix plus HttpOnly, Secure and SameSite=Strict.
  • CSRF: constant-time token comparison on state-changing requests and a short-lived double-submit login token. Unknown users receive equivalent Argon2 work; bounded client/account rate limits and password concurrency resist enumeration and resource exhaustion. Multi-process deployments additionally rate-limit /login at the shared ingress.
  • Preview artifacts and uploaded media require a valid session; sensitive responses use HSTS and Cache-Control: no-store.
  • Accounts live in the storage database (shared migrations) and are never exported.

What works today (Milestone 3, phases 1–8)

  • Dashboard: content by workflow status, the translation coverage matrix (missing/outdated/complete per language), a live validation report with the publish-gate verdict.
  • Articles: list with per-language state badges, create, EN source editor (slug, category, tags, cover) with a preview rendered by the builder's own Markdown renderer (raw HTML disabled), and a side-by-side translation editor per language — the EN source read-only next to the translation form.
  • Pages and sections: metadata editor, ordered typed sections (add/reorder/remove), a dynamic field editor with kind-aware suggestions, and per-section side-by-side translation.

Editing a source marks its translations outdated automatically (checksum model). Statuses are displayed but transitions arrive with the workflow phase.

  • Media library: raster uploads validated server-side (MIME sniffed from bytes, bounded read, byte/dimension/pixel limits); active SVG is rejected and exclusive writes never replace paths. EN alt text is mandatory and deletion remains reference-checked.

  • Menu manager: explicit navigation with per-language labels, ordering and external links — defined items replace the automatic menu, an empty list keeps it.

  • Editorial notes & quick actions: a team-only note trail on every entry (never published; author-or-admin removal) and a per-row actions dropdown on the lists — workflow moves and trash without opening the editor.

  • Design preview + autosave: each editor frames the entry rendered by the real builder and theme. Valid edits autosave after a short debounce and refresh only that entry; invalid intermediate forms pause without overwriting content, while explicit saves retain revision semantics.

  • Featured & authorship: a Featured flag (featured articles lead the home highlight; listings stay recency) and an editorial byline rendered by the themes.

  • Media filters: server-side search (id, path, type, alt) and quick views — images only, missing translated alt — with a shown-of-total counter.

  • Users screen: admins create accounts (role + panel language), change roles and delete from the panel — you cannot delete yourself, and the last admin can neither be deleted nor demoted.

  • Duplicate & per-entry preview: one-click duplicate-as-draft (collision-safe ids, workflow reset) and a direct jump from each editor to the entry's URL inside the preview build.

  • Trash: reversible deletion — trashed articles and pages disappear from builds, validation, export and lists; restore is exact, and "Delete forever" (admin-only, trash-only) is the panel's single permanent removal.

  • Revisions: every save keeps a snapshot (newest 20 per entity); editors show the history with a per-revision diff against the current content and one-click restore — the restore itself is recorded, so it is always undoable.

  • Scheduled publishing: articles and pages take an optional "Publish at (UTC)" moment — published content stays out of every build until a build runs after it (the build is the clock on a static host; a CI cron recipe ships in the admin guide). Scheduling never bypasses validation or the publish gate.

  • Markdown editor: article bodies get a real editing surface (EasyMDE, MIT, vendored — no CDN) with a localized formatting toolbar; the builder's server-rendered preview remains the single truth for what publishes, and the plain textarea still works without JavaScript.

  • Panel language (i18n): real gettext localization (PT-PT, ES, FR, DE shipped alongside English) — each editor picks a language in the user menu (or cms admin create-user --language), with the browser's language as fallback; an anti-drift test keeps every catalog complete.

  • Workflow and publishing: role-gated transitions (draft → review → published → archived, including one-click unpublish straight back to draft; publishing needs the publisher rung), the publish gate blocking on the entity's validation errors, and the Publishing panel — preview builds served under /preview/, build & export with target extras written to the project output, every run recorded on the dashboard.

  • Validation report (dashboard + Publishing panel): the gate never hides its work — a callout states the verdict and the validated scope (articles, pages, media, languages), a table lists every rule that ran with its outcome (passing rules included), and each issue links straight to the edit screen of the content that caused it. cms validate prints the same per-rule outcomes in the terminal. The seeded demo keeps one article in review with a missing DE translation, so the report always shows a live warning in action.

  • Hardening: enforced CSP/HSTS/no-store headers, authenticated preview/media, hostile-input regressions, a required dependency/static security gate, managed CodeQL and the axe accessibility gate. The full admin guide lives in docs/ADMIN_GUIDE.md, anti-drift-checked.

Milestone 3 is complete: the full editorial cycle — create, translate, review, publish, build, export — works from the browser, with every security and accessibility gate green in CI. (role-gated transitions, publish gate, builds/exports from the panel), and the hardening pass (CSP, security suites, axe over admin pages). The phased plan lives in docs/PLAN.md.

The demo snapshot

python -m cms_admin.demo_export --storage content.sqlite3 --out _site/admin boots the real admin on a throwaway copy of the content, signs in as a generated demo user and captures every editorial page as inert static HTML (links prefixed, CSRF stripped, forms neutralized, read-only banner). The demo deploy runs it automatically.

Clone this wiki locally