Admin Manual
Complete operational guide for Project Syrup administrators.
Roles & Permissions
Every admin account carries exactly one role. Role determines what menu items appear, what API calls succeed, and which confirmation requirements apply.
| Role | Who should have it | Key capabilities |
|---|---|---|
| super_admin | Owner / technical operator | Everything โ including admin management, system settings, all login history, and server settings. |
| admin | Trusted staff | Full waffle management (create, edit, archive, delete), reports, audit log, login history for self and waffle_managers. Cannot manage other admins. |
| waffle_manager | Helpers who run drops | Create and manage waffles, view reports. No archive/delete, no admin management, no audit log, no other users' login history. |
Detailed permission matrix
| Action | super_admin | admin | waffle_manager |
|---|---|---|---|
| Create / edit waffles | โ | โ | โ |
| Manage spots (pay / release) | โ | โ | โ |
| Set / change / clear winner | โ | โ | โ |
| Archive / unarchive waffles | โ | โ | โ |
| Delete waffles (permanent) | โ | โ | โ |
| View reports | โ | โ | โ |
| View audit log | โ | โ | โ |
| View all login history | โ | โ | โ |
| View own login history | โ | โ | โ |
| View waffle_manager login history | โ | โ | โ |
| Create / deactivate admins | โ | โ | โ |
| Change another admin's role | โ | โ | โ |
| Reset another admin's password | โ | โ | โ |
| System settings | โ | โ | โ |
| View users registry (top-level nav) | โ | โ | โ |
| Manage payment methods v0.1.22 | โ | โ | โ |
Logging In
Navigate to /admin/login and enter your username and password.
If you are already logged in and visit the login page, you are redirected to the dashboard automatically โ you will not see the login form.
Login Lockout
The throttle tracks failed attempts by the combination of IP address + username. After 5 consecutive failed attempts, that IP/username pair is locked out for 15 minutes. The lockout counter is held in memory (not persisted to the database) and is automatically cleaned up after it expires.
A successful login resets the counter for that IP/username pair. If you are locked out, wait 15 minutes for the lockout to expire automatically โ there is no manual unlock UI. The lockout is per-IP, so a super_admin cannot unlock your account early; you must wait out the timer or try from a different IP.
Forgot Your Password?
Click Forgot password? on the login page. Enter your username. A single-use reset token is generated.
Reset tokens expire after 1 hour and are single-use. Once used, they cannot be replayed even if the expiry hasn't passed. To complete the reset, navigate to /admin/reset-password and submit the token and new password.
Navigation
The admin top bar contains the following links, visible based on your role:
| Nav item | Visible to | Description |
|---|---|---|
| Dashboard | All | Active and archived waffle list |
| New Waffle | All | Shortcut to the create waffle form |
| Reports | All | Analytics โ drought list, power buyers, monthly activity, spot velocity |
| Users | All | Buyer Instagram handle registry with search and pagination |
| Management dropdown | admin, super_admin | Renamed from "Admin Tools" in v0.1.22. Contains: Audit Log (admin+), Payment Methods (admin+), Admin Users (super_admin only), Server Settings (super_admin only) |
| Your display name (top-right) | All | Profile Settings, My Login History, About, Logout |
/admin/settings#login-history. The full admin login history page (admin+ roles) still exists at /admin/login-history but is no longer in the dropdown menu โ open it directly or via the Settings page tab.
How your name appears in the nav
The top-right button shows your name using this priority:
- Display Name โ if set and non-empty, it is used as-is
- First Name + Last Name โ if both are set
- First Name alone โ if only first name is set
- Username โ fallback when no profile fields are filled in
Set these fields under Settings โ Profile.
Dashboard
The dashboard is your home screen after login. It shows all waffles and a summary card for each one.
Two tabs control which waffles are shown:
- Active (default) โ non-archived waffles, newest first
- Archived โ waffles you've archived after completion
Each waffle card shows: title, price per spot, total spots, status badge (Active / Completed), counts for available / pending / paid spots, a fill progress bar, and an image thumbnail if an image_url is set on the waffle. The nav bar footer also shows total waffle count and active waffle count.
From a waffle card you can click:
- Manage โ opens the spot management view
- Edit โ opens the edit form
- Archive / Unarchive โ toggles archived state (admin or higher)
Creating a Waffle
Click New Waffle from the dashboard. Fill in the form fields described below.
Fields & Validation
| Field | Required | Validation | Notes |
|---|---|---|---|
| title | โ | Non-empty string | Used as the page heading and generates the URL slug (e.g. "Blue Wig #3" โ /waffle/blue-wig-3). Slug is set at creation time and cannot be changed. |
| total_spots | โ | Integer โฅ 2 | Creates exactly that many numbered spots (1 through N) in a single transaction. Cannot be changed after creation. |
| spot_price | โ | Integer > 0 (cents) | Display only โ Syrup does not process payments. Stored as an integer (e.g. 5 = $5). Used in revenue totals in reports. |
| item_count | โ | Integer 1โ10 (default 1) | Added in v0.1.20. Number of distinct items being waffled in a single drop. When > 1, you draw that many winners (each on a distinct paid spot) instead of one. Useful when one waffle covers, e.g., 3 different pieces in a set. |
| description | โ | Free text | Optional subtitle shown below the title on the manage page and public waffle page. |
| payment_methods[] | โ | UUID array | Replaces payment_info as of v0.1.22. Multi-select checkboxes pick from your stored payment methods (Venmo, PayPal, CashApp, Zelle). Selected methods are grouped by type and rendered with icons + clickable links on the public waffle page. Manage the pool under Management โ Payment Methods. |
| instagram_media_links[] | โ | URL array | One or more Instagram post links showing the item. Empty strings are stripped out automatically. Rendered as "IG Post" buttons on both admin and public pages. |
Quick-fill Templates
The New Waffle form includes four pre-fill buttons to jumpstart common configurations:
| Template | Total Spots | Price per Spot |
|---|---|---|
| Small | 10 | $2 |
| Standard | 25 | $5 |
| Medium | 50 | $3 |
| Large | 100 | $2 |
Clicking a template fills the Total Spots and Spot Price fields โ you still need to enter a title and any media links.
Editing a Waffle
From the dashboard card or the manage view, click Edit. You can change:
- Title
- Description
- Spot price
- Payment info
- Instagram media links (add, remove, or reorder)
All edits require a valid CSRF token (automatically included in the form). The edit is recorded in the audit log as update_waffle.
Archive & Delete
Archiving
Archive a waffle to move it off the active dashboard without destroying any data. Archived waffles appear under the Archived tab. Buyer stats, activity history, and all spot records are preserved. Only admin and super_admin can archive.
Click Unarchive from the Archived tab to restore it to active if you archived by mistake.
Both actions are recorded in the audit log as archive_waffle / unarchive_waffle.
Permanent Deletion
Permanent deletion requires two confirmations to prevent accidents:
- Type the word
DELETEexactly (case-sensitive) into the confirmation field. - Enter your current admin password.
Both checks run server-side. If either fails, the delete is aborted and you are redirected back. When it succeeds, the waffle, all its spots, and its activity history are removed permanently. The audit entry for the delete is still written because it is recorded before the deletion occurs.
CSV Export
Export a waffle's full spot list as a CSV file for external reconciliation or record keeping. The export button appears in the waffle info header on the manage page.
The export is also available without authentication at GET /api/waffles/:slug/export, so you can link buyers directly to a post-draw results file if you choose.
Columns in the export: spot number, status, claimed_by_handle, claimed timestamp, paid timestamp.
Public Buyer Experience
It's worth understanding what your buyers see, because it shapes how you set up waffles.
The public page lives at /waffle/:slug and shows: title, description, payment info,
Instagram media link buttons, an image thumbnail (if image_url is set), a fill
progress bar, and the spot grid.
Manual Claim Flow
- Buyer taps one or more available spots in the grid โ they highlight as selected.
- They enter their Instagram handle in the input below the grid (with or without the
@). - They tap Claim Spots. The selected spots transition to Pending for everyone watching the page (WebSocket broadcast).
- Buyer sends payment to you via DM / Venmo / CashApp out-of-band.
- You mark the spots paid from the manage view.
Random Spot Selection
How it works for buyers:
- Buyer enters their Instagram handle.
- Instead of tapping the grid, they enter a count (e.g.
5) into the random-spots input and tap Pick Random Spots. - The server picks that many available spots at random under a transactional row lock and claims them in one shot.
- The claimed spot numbers are returned to the buyer and broadcast via WebSocket to everyone watching.
Partial Fulfillment
If the buyer asks for more spots than are currently available, the server claims as many as it can and returns both requested_count and claimed_count in the response. There is no client-side maximum cap โ the server is authoritative.
Mutual exclusivity with manual selection (v0.1.22-beta.0)
To prevent confusion, the random-claim controls are disabled while any manual spot is selected, and vice versa. A buyer either taps specific spots or asks for N random ones โ not both in the same submission.
Same handle, same rate limits
Random claims hit the POST /api/claims/random endpoint, which reuses the same Instagram handle normalization, rate limiting, and WebSocket broadcasting as manual claims. The user registry is updated the same way.
Manage View
Click Manage on any waffle card. This is your primary working screen during a live drop. It has five sections: a waffle info header, a stats bar, the spot grid, a pending claims list, and the winner section.
Stats Bar
A row of tiles at the top of the manage view shows real-time counts:
| Tile | What it counts |
|---|---|
| Available | Spots no one has claimed yet |
| Pending | Spots claimed but payment not confirmed |
| Paid | Spots with confirmed payment |
| Total | Calculated revenue โ paid spots ร spot price |
Spot Grid
A grid of numbered buttons โ one per spot. Each button is color-coded by status:
| Status | Color | Displayed info | Clickable? |
|---|---|---|---|
| Available | Green | Spot number only | No (buyers claim these) |
| Pending | Yellow/amber | Spot number + Instagram handle | Yes โ opens pay/release actions |
| Paid | Red | Spot number + Instagram handle | No (use pending claims list to release) |
| Winner | Purple/secondary + ๐ | Spot number | No |
| Loser | Grey, dimmed | Spot number | No |
Loser is a derived status set automatically on all paid spots (other than the winner) when a winner is entered. It feeds into buyer win/loss statistics. Buyers do not see the loser label on the public page โ they just see the spot as completed.
A WebSocket connection icon in the top-right of the grid shows live connection status. Updates from other admin sessions or buyer claims appear instantly without a page refresh.
Bulk Pay Mode
For fast payment reconciliation, click Bulk Pay Mode in the top-right of the spot grid. The grid enters selection mode: tap any pending spots to select them (they highlight). A footer bar shows how many are selected and a Mark Paid button that sends all selected spots to the pay API simultaneously.
Bulk pay mode is only shown when the waffle is still active (not completed).
Pending Claims List
Below the spot grid, the Pending Claims panel shows every pending spot as a card with:
- Spot number
- Instagram handle of the claimer (
@handle) - Timestamp of when the spot was claimed
- Mark Paid button
- Release button
This is the most efficient way to work through pending claims one at a time as DM payments come in. When there are no pending claims, it shows "No pending claims."
Spot Actions
Mark Paid
Transitions a Pending spot to Paid. Available from both the pending claims list and the spot grid click-handler. A WebSocket broadcast updates all connected clients immediately. Recorded in the audit log as mark_paid.
Release
Transitions a Pending or Paid spot back to Available. Use this when a buyer claims a spot but doesn't pay, or if you accidentally marked the wrong spot paid. Broadcasts a spot update to all clients. Recorded as release_spot.
Winner Flow
The Set Winner section appears at the bottom of the manage page for active waffles.
You pick the winning spot(s) from a dropdown listing every paid spot with its Instagram handle (e.g. Spot #7 โ @dani_boo_glass), then click Set Winner.
Single-winner waffles (item_count = 1)
The default case. Pick one spot from the dropdown. What happens when you click Set Winner:
- The winning spot's status is changed to Winner.
- All other spots with status Paid are updated to Loser.
- The winner's buyer stats (
total_wins) increment by 1. - All losing buyers' stats (
total_losses) increment by 1. - The waffle status changes from Active to Completed.
- The waffle's
winning_spot_numbersandwinning_instagram_handlesarrays are populated. - A
waffle_completedWebSocket broadcast fires to all connected clients. - A winner banner appears at the top of the manage page.
Multi-item waffles (item_count > 1)
For waffles where you set item_count above 1 at creation, the Set Winner UI shows one dropdown per item.
You pick a distinct paid spot for each item, then click Set Winner once to commit all of them.
- Each chosen spot is marked Winner.
- All remaining paid spots are marked Loser.
- Each winner's
total_winsincrements by 1 (so picking the same buyer twice is intentionally not allowed โ dropdown values must be distinct). - The public waffle page renders a winner list, one row per item.
Recorded in the audit log as set_winner.
Changing or Clearing Winners
After winners are set, the manage page swaps the Set Winner UI for Change Winner and Clear Winner controls.
Clear Winner
Removes all winners on the waffle. Every winning spot reverts to Paid. All loser spots revert to Paid.
Buyer win/loss stats are fully recalculated for all affected spots.
The waffle status reverts to Active.
Fires a winner_cleared WebSocket broadcast.
Recorded as clear_winner.
Change Winner
Reassigns one or more winners. The dropdowns are pre-populated with the current winning spots; change any of them to a different paid spot and click Change Winner.
Old winning spots that are no longer in the selection revert to Paid; new selections become Winner; loser status is recomputed across all paid spots.
Buyer stats are recalculated for all affected spots.
Fires a winner_changed WebSocket broadcast.
Recorded as change_winner.
Admin User Management
Available to super_admin only. Navigate to Management โ Admin Users. The page shows a table of all admin accounts โ active and inactive โ with username, email, role, status, and last login. An inline role-permissions guide is shown at the top of the page (collapsed on mobile, always visible on desktop) to help you choose the right role when creating a new admin.
Creating an Admin
Click New Admin and fill in the form:
| Field | Required | Notes |
|---|---|---|
| username | โ | Must be unique. Used at login. Cannot be changed after creation. |
| โ | Must be unique. Currently used for password reset lookup and profile only (no automated email). | |
| password | โ | Must pass the password policy (see below). The admin should change it on first login. |
| role | โ | One of super_admin, admin, waffle_manager. Defaults to admin if an invalid value is submitted. |
| first_name | โ | Optional โ used in the display name priority chain. |
| last_name | โ | Optional. |
| display_name | โ | Optional โ takes top priority in the nav display name. |
All form errors are returned inline on the page (username required, email required, password required, password policy failures). The new admin is created active and with timezone set to UTC by default.
Recorded as create_admin.
Changing a Role
Click the role badge on an admin row in the table. Select the new role from the dropdown. Role changes for demotions (e.g. admin โ waffle_manager) require your current password as confirmation before the API call proceeds.
Recorded as update_admin_role.
Resetting Another Admin's Password
Click Reset Password on an admin row. Enter a new temporary password (must pass the password policy). The admin's current session is not invalidated โ they will need to log out and back in with the new password. Send the temporary password to them securely and ask them to change it immediately.
Recorded as reset_admin_password.
Deactivating an Admin
Click Deactivate on an admin row and confirm your password. The account is set to active = false. Deactivated admins cannot log in โ authentication returns an "account deactivated" error even with a correct password. Their audit history, login history, and any waffles they created are fully preserved.
To reactivate a deactivated admin, use the API directly (PATCH /api/admin/admins/:id with {"active": true}) โ there is no UI reactivation button at this time.
Recorded as deactivate_admin.
Payment Methods v0.1.22
Added in v0.1.22, the payment methods feature replaces the free-text payment_info field on waffles with a structured pool of stored payment options.
You maintain one shared list at /admin/payment-methods; each waffle picks which methods to display via multi-select checkboxes on the create/edit form.
Public waffle pages render the selected methods grouped by type, with icons (Venmo, PayPal, CashApp, Zelle) and clickable links.
Available to admin and super_admin. Navigate to Management โ Payment Methods.
Managing the Pool (CRUD & Soft Delete)
The Payment Methods page lists every stored payment option. Each row has these fields:
| Field | Description |
|---|---|
| type | One of venmo, paypal, cashapp, zelle. Determines which icon and link format the public page uses. |
| display_name | The label shown to buyers (e.g. "Venmo โ @notfixingit" or "PayPal โ Backup"). Use this if you have multiple accounts of the same type. |
| handle_or_url | The actual handle (@notfixingit) or a full URL. The public page builds a clickable link from this. |
| is_active | Soft-delete flag. Inactive methods stay in the database (preserving history on past waffles) but no longer appear in the multi-select on new/edited waffles. |
Create / Update
Click Add Payment Method to create one, or click any row's edit control to update an existing entry. Type, display name, and handle/URL are all required.
Soft delete (deactivate)
Click Deactivate on a row to mark it inactive. The record is preserved so past waffles that referenced it still render correctly. If the method is currently selected on one or more active waffles, you get a usage warning before the deactivation goes through โ that's the system telling you the deactivation will hide an option that's currently in use.
To reactivate a method later, the equivalent API call sets is_active = true; there is currently no dedicated reactivate button in the UI.
Selecting Methods on a Waffle
On the create-waffle and edit-waffle forms, the old payment_info textarea is gone.
In its place is a multi-select checkbox list of every active payment method, grouped by type.
Check the ones you want this waffle to advertise; uncheck the ones you don't.
The selected methods are stored as an association โ not a copy โ so editing a payment method's display name or handle later will update every waffle that uses it. (This is usually what you want; if you need a one-off variant, create a new payment method with a distinct display name.)
What buyers see
On the public waffle page, the payment section now renders one row per active method on this waffle, with:
- An icon for the payment type (Venmo, PayPal, CashApp, or Zelle)
- The display name
- The handle or a clickable link that opens the payment app (or web fallback)
Multiple methods of the same type (e.g. two Venmo accounts) are grouped together.
Share to Instagram v0.1.23-beta
Waffle can generate a branded PNG card and a customizable caption for each drop, so you can share a waffle directly to your Instagram Story or feed without leaving the app. The feature has two parts: Share Templates (reusable captions, managed in Settings) and the Share to Instagram section on each waffle's manage page.
Share Templates
Navigate to Management โ Settings โ Share Templates. Available to all roles. Templates are reusable caption bodies that support placeholder variables substituted at render time:
| Placeholder | Replaced with |
|---|---|
{title} | Waffle title |
{price} | Price per spot (e.g. $5.00) |
{spots} | Total number of spots |
{url} | Full public claim URL (https://yourhost/waffle/:slug) |
You can create multiple templates (e.g. a short teaser, a launch caption, a last-call reminder) and set one as the default. The default is pre-selected whenever you open the share editor on a waffle.
Waffle Card PNG
Every active waffle has a downloadable PNG card at /waffle/:slug/card.png. Two formats are available via the ?format= query parameter:
- Story (default) โ 1080ร1920 vertical, sized for Instagram Stories
- Square โ 1080ร1080, sized for feed posts
The card shows the waffle title, spot price, spots remaining, and the public claim URL. It's generated with Inter font and Project Syrup branding, then cached on disk. The cache is automatically invalidated when you update the share message or click Regenerate Card.
Share to Instagram (on the Manage Page)
At the bottom of each waffle's manage page (Management โ Waffles โ [waffle] โ Manage) there is a Share to Instagram card. From here you can:
- Pick a template from your saved templates (default is pre-selected).
- Edit the rendered caption โ placeholders are substituted live. Click Save Message to persist the custom caption to this waffle.
- Copy the public claim URL with one tap.
- Toggle Story / Square format and click Download Card to save the PNG.
- Click Regenerate Card to force a fresh render if you've made changes after the card was first generated.
- Click Copy Message to copy the final caption to your clipboard.
Reports
Navigate to Reports in the top nav. Available to all roles. Reports are rendered client-side from JSON endpoints โ the page loads first, then data is fetched via JavaScript.
Each report has a date range selector. The default range varies by report.
Drought List
Shows buyers who have been entering waffles but haven't won in a long time.
Sorted by longest_drought โ the number of days since their last win (buyers who have never won are listed as 99,999 days).
Columns: Instagram handle, total waffle entries in the date range, last entry date, days since last win.
Use this to identify loyal buyers who are overdue for attention โ or to reward buyers who've been entering for a long time without a win.
Power Buyers
Ranks buyers by the number of paid spots across all waffles in the date range. Shows your most financially committed participants.
Columns: Instagram handle, total spots claimed (paid), total amount spent, win rate percentage.
Win rate is calculated as: wins รท (wins + losses) ร 100, rounded to one decimal place.
A buyer with no completed waffles (only pending spots) shows 0%.
Default limit: top 20 buyers.
Monthly Activity
Aggregates three streams of activity by calendar month:
- Waffles โ number of waffles created that month
- Spots Claimed โ number of spots claimed (any status) that month
- Revenue โ sum of
spot_pricefor all spots marked paid that month
This is the primary tool for tracking the health and growth of your waffle operation over time.
Spot Velocity
Measures how quickly your waffles fill up, grouped by waffle status (active vs completed):
- Waffle Count โ number of waffles in the group
- Avg Time to First Claim โ average hours from waffle creation until the first spot was claimed
- Avg Time to Completion โ average hours from waffle creation until
completed_at(only meaningful for completed waffles)
Useful for understanding buyer demand and deciding how large to make your waffles.
Audit Log
Available to admin and super_admin. Navigate to Management โ Audit Log.
Every state change performed by any admin is written to the audit_log table.
Writes are asynchronous (in a goroutine) so they do not slow down the operation that triggered them.
Each entry records: timestamp, the acting admin's ID, the action code, the affected entity type and ID, a human-readable detail string, and the admin's IP address at time of action.
Audited Actions
- create_waffle Waffle created โ includes the title
- update_waffle Waffle edited โ includes the new title
- archive_waffle Waffle archived
- unarchive_waffle Waffle restored from archive
- delete_waffle Waffle permanently deleted
- mark_paid Spot marked paid โ includes spot number
- release_spot Spot released back to available โ includes spot number
- set_winner Winner set โ includes winning spot number
- clear_winner Winner cleared
- change_winner Winner changed โ includes new winning spot number
- create_admin New admin created โ includes username and role
- update_admin_role Admin role changed โ includes new role
- update_admin_profile Admin profile updated
- reset_admin_password Another admin's password was reset
- deactivate_admin Admin deactivated โ includes username
- update_whois_settings WHOIS server setting updated โ includes new value
- update_setting Any system setting updated โ includes key and new value
Filters & Export
The audit log page has filter controls:
- Admin โ filter by acting admin (super_admin can see all; admin sees self and waffle_managers)
- Action โ filter by action code (e.g.
mark_paid) - Target type โ filter by entity type (
waffle,spot,admin,settings) - Date range โ from/to timestamps
Results are paginated (default 20 per page). Click Export CSV to download the current filter results as a CSV file. The export URL includes URL-encoded filter parameters so it is reproducible and shareable.
Audit log entries older than the configured retention period (default 90 days) are automatically purged. Change this under Server Settings.
Login History
Navigate to your username dropdown โ My Login History for your own logins. The full admin login history (admin+ roles) lives at /admin/login-history โ it's still accessible but no longer in the Management dropdown menu as of v0.1.22.
Each login entry captures:
| Field | Description |
|---|---|
| ip_address | IP address the login came from |
| browser | Browser name parsed from the User-Agent header |
| os | Operating system parsed from User-Agent |
| device_type | Desktop, mobile, or tablet โ parsed from User-Agent |
| ip_org | Organization name from WHOIS lookup (async, may be empty for private IPs) |
| ip_country | Country from WHOIS lookup |
| ip_city | City from WHOIS lookup |
| ip_asn | ASN from WHOIS lookup |
| whois_server | Which WHOIS server was queried (recorded for auditability) |
WHOIS lookups are asynchronous with a 10-second timeout โ they do not slow down the login flow. Private/RFC1918 addresses (10.x, 172.16โ31.x, 192.168.x) are skipped and WHOIS fields are left empty.
Visibility by role
- waffle_manager โ own entries only
- admin โ own entries + all waffle_manager entries
- super_admin โ all entries; can also filter by admin
Login history entries older than the configured retention period (default 90 days) are purged automatically. Change this under Server Settings.
Users Registry
Navigate to Users in the top nav. Available to all roles.
The users registry is a deduplicated list of every Instagram handle that has ever claimed a spot.
On startup, the app backfills all existing claimed_by_handle values from the spots table into the users table automatically.
Every new spot claim upserts the handle if it isn't already present.
Columns
| Column | Description |
|---|---|
| Instagram Handle | Clickable link โ opens the public buyer stats page at /buyer/:handle showing win/loss history, luck rating, and trophy case. A shareable card version is available at /buyer/:handle/card (v0.1.23-beta.0+). |
| Created At | When this handle was first seen by the system (first claim, or backfill timestamp). |
| Updated At | When the record was last touched (e.g. a new claim by the same handle). |
Search and pagination
- A search box at the top filters by Instagram handle (substring match). A Clear button appears when a search is active.
- Results are paginated โ 25 per page by default, up to 100 per page via the
per_pagequery param. - The pagination footer shows current page, total pages, and total user count.
Settings โ Timezone
Click your username โ Settings. The timezone dropdown accepts any valid IANA timezone string (e.g. America/New_York, Europe/London, Asia/Tokyo). Invalid values are rejected by the server.
All timestamps shown in the admin UI โ waffle creation dates, spot claimed/paid times, login history โ are displayed in your selected timezone.
Settings โ Change Password
From Settings, scroll to the Change Password section. You must provide your current password before a new password is accepted.
Password policy
The minimum password length is configurable (default 8, stored in password_min_length system setting). Passwords are also checked against a blocklist of common passwords. Blocked values include:
password password1 password123 12345678 123456789
qwerty qwerty123 admin admin123 letmein welcome
welcome123 iloveyou monkey dragon football baseball
abc123 111111 00000000
Passwords are hashed with bcrypt before storage. The plaintext is never logged or stored.
Settings โ Profile & Social Links
From Settings, the profile section allows you to set:
| Field | Notes |
|---|---|
| first_name | Used in the display name priority chain |
| last_name | Combined with first_name for nav display |
| Must be unique across all admin accounts. Used for password reset. No automated email is sent. | |
| social_links | Array of platform + handle pairs |
Social links
You can store up to 10 social links. Each link has a platform and a handle. Valid platforms:
Handles are limited to 100 characters each. Duplicate platforms in the same admin's profile are not allowed. These are stored for your profile only โ they are not displayed publicly.
System Settings
super_admin only.
Navigate to Management โ Server Settings.
Each setting is stored as a key/value pair in the system_settings table, so changes persist across restarts.
| Setting key | Default | Description |
|---|---|---|
| whois_server | whois.pwhois.org |
WHOIS server used for async IP enrichment on admin logins. Must be a reachable hostname. Changes take effect on the next login. |
| jwt_expiration_hours | 24 |
How long admin JWT tokens remain valid (in hours). Changing this does not invalidate existing tokens โ they expire at their original time. Must be a positive integer. |
| password_min_length | 8 |
Minimum character length for admin passwords. Applied to new passwords and password changes. Does not enforce retroactively on existing passwords. |
| audit_retention_days | 90 |
Audit log entries older than this many days are purged by the automatic retention job. Set to a higher value to retain more history. |
| login_history_retention_days | 90 |
Login history entries older than this many days are purged. Separate from audit log retention so you can tune them independently. |
Changes to any system setting are recorded in the audit log as update_setting (or update_whois_settings for WHOIS specifically), with the old key and new value captured in the details field.
password_min_length will apply the next time someone submits a password form. A change to audit_retention_days will apply the next time the retention cleanup job runs.