Commit Graph

50 Commits

Author SHA1 Message Date
Matthew Meszaros 9b948e326c chore(security): sync admin package.json vite range to ^7.3.6 to match the security-patched lockfile 2026-06-28 06:29:18 +00:00
Matthew Meszaros 9eb633414f fix(security): bump admin vite to 7.3.6 and form-data to 4.0.6 to clear CVE-2026-53571 and CVE-2026-12143 2026-06-28 06:29:01 +00:00
Matthew Meszaros 587eae774e feat: serve the entire customer API (auth + resources) only under /v1 with no unversioned alias, and repoint the web and admin clients to the versioned base accordingly 2026-06-13 07:18:23 +02:00
Matthew Meszaros 1afe453eb0 feat: add deliverability controls
Add seed inbox-placement testing with admin management, placement result polling, and seed mailbox persistence.

Add pre-send email verification, invalid-recipient skipping, warmup-health campaign gating, and RFC 8058 one-click unsubscribe headers/endpoints.
2026-06-03 16:47:53 +02:00
Matthew Meszaros b0b712a43f feat: refine warmup content admin
Treat warmup content as a shared library in settings and admin tables, and realign the conversation pick index with segment-based selection.

Add generation job details and fix warmup placement displays so backend percentages and provider counts render correctly.
2026-06-03 11:32:09 +02:00
Matthew Meszaros 0b6dc6ddfd feat: split warmup content admin pages
Replace the monolithic warmup content admin page with routed overview, library, generate, jobs, and settings pages.

Add shared warmup content layout, tab navigation, and reusable presentation helpers for the section.
2026-06-03 11:14:47 +02:00
Matthew Meszaros 2009780beb feat: add warmup batch generation
Add OpenAI Batch API support for warmup content generation, including job metadata, polling, cancellation, and completed-batch ingestion.

Share the generation prompt between sync and batch modes and humanize/lint generated threads before storing them in the warmup content library.
2026-06-03 11:14:33 +02:00
Matthew Meszaros 38cbcd281e feat: add warmup star engagement
Add star-rate settings to warmup content controls and include star actions in generated engagement plans.

Execute Gmail stars via STARRED labels while keeping IMAP behavior a no-op to avoid duplicate flagging.
2026-06-03 06:26:47 +02:00
Matthew Meszaros 47bdf66899 feat: improve warmup health analytics
Record warmup spam placements by recipient provider and surface provider breakdowns in the admin health summary.

Track warmup replies and use stable static conversation ids so warmup analytics can correlate content reliably.
2026-06-03 06:26:39 +02:00
Matthew Meszaros 15ef9d4994 feat: add warmup content controls
Add warmup content generation and admin review surfaces, plus mailbox warmup appeal/status APIs.

Track warmup engagement and tampering signals so unsafe mailboxes can be handled by the warmup flow.
2026-06-03 05:05:53 +02:00
Matthew Meszaros cf5ca0e649 feat: consolidate admin infrastructure settings
Replace separate backend settings pages with a single infrastructure view, remove the egresses route, and update admin navigation/readme copy.
2026-06-02 15:54:47 +02:00
Matthew Meszaros e740aebba8 feat: upgrade admin management explorers
Wire expanded admin API contracts into campaigns, discounts, enterprise, limit request, outreach, plan, worker detail, and provisioning admin views.
2026-06-02 15:54:40 +02:00
Matthew Meszaros 0e31748662 feat: add worker explorer filters
Convert the managed workers page into a faceted explorer with client-side filtering, sorting, CSV metadata, and worker detail navigation.
2026-06-02 05:44:43 +02:00
Matthew Meszaros 6bc6c066fc feat: add faceted admin data browsers
Upgrade users, organizations, and mailboxes admin pages with explorer filters, date and numeric facets, sortable tables, and scoped navigation params.
2026-06-02 05:44:39 +02:00
Matthew Meszaros 3d0b514eb7 feat: add admin explorer filter primitives
Expand the admin explorer control set with select, toggle, date-range, and number-range filters plus shared date-range serialization helpers.
2026-06-02 05:44:35 +02:00
Matthew Meszaros 0b6c394e89 feat: wire admin explorer query params
Add a shared admin search query serializer and expose the expanded user, organization, and mailbox filter contracts to the frontend clients.
2026-06-02 05:44:31 +02:00
Matthew Meszaros 7906363c16 feat: refine the admin data explorer (organized facet rail, result count, icon toolbar, crisp table) 2026-06-01 18:14:10 +02:00
Matthew Meszaros 8be0aa0269 feat: Mailboxes browser + browse-mailboxes-by-org cross-entity filter 2026-06-01 18:01:40 +02:00
Matthew Meszaros 072e7a7b6e feat: data-explorer (filter rail + sortable, cursor-paged table) for Users and Organizations 2026-06-01 17:56:03 +02:00
Matthew Meszaros 412a4c0baa fix: broken admin list endpoints (uuid casts, campaign/plan columns, warmup pools shape) 2026-06-01 17:44:13 +02:00
Matthew Meszaros 86f72b8c53 feat: refine admin dashboard chrome
Align the sidebar header with the topbar, use the Warmbly logo, and let dashboard content use the full available width.
2026-06-01 17:41:13 +02:00
Matthew Meszaros df658ad9a0 feat: harden admin warmup error states
Wire the shared error state through warmup admin queries and guard summary cards against partial API responses.
2026-06-01 17:41:10 +02:00
Matthew Meszaros defe511bd3 feat: show full admin user list errors
Use the shared error state on user and organization admin tables so failed loads expose the real API message and request ID.
2026-06-01 17:41:05 +02:00
Matthew Meszaros 0e3bbcaca0 feat: add admin query error state
Introduce a shared admin error panel that displays full API failure details and offers an inline retry action.
2026-06-01 17:41:01 +02:00
Matthew Meszaros 2c8beefe0d feat: surface admin api error metadata
Capture backend error codes and request IDs in the admin APIError type so failed admin views can show actionable diagnostics.
2026-06-01 17:40:57 +02:00
Matthew Meszaros 910fac9808 feat: declutter the admin login (no dev env pill, flat mail badge, no load animation) 2026-06-01 16:49:46 +02:00
Matthew Meszaros 4944689e57 feat: add a complete admin favicon set (ico, png, apple-touch, manifest) 2026-06-01 16:43:27 +02:00
Matthew Meszaros abb38b0f51 feat: animate the admin login steps and add an OTP code input 2026-06-01 16:20:32 +02:00
Matthew Meszaros f62312d674 feat: optically center the Warmbly mark in the admin favicon 2026-06-01 16:19:29 +02:00
Matthew Meszaros f2913249b4 feat: implement the two-step email-code admin login flow 2026-06-01 13:58:06 +02:00
Matthew Meszaros bcc861ef7e feat: theme the admin favicon with the Warmbly mark in red 2026-06-01 13:19:37 +02:00
Matthew Meszaros d554fdec4a feat: redesign the admin login page 2026-06-01 13:12:32 +02:00
Matthew Meszaros 8b28b86b7f feat: support exposed local dev hosts
Add PUBLIC_HOST wiring for local web, admin, site, realtime, and backend CORS so the native dev stack can be reached over Tailscale or LAN without changing localhost defaults.
2026-06-01 05:51:02 +02:00
Matthew Meszaros 6766031cc5 feat: add discount code support for checkout and plan changes 2026-05-29 05:49:19 +00:00
Matt 3ffa416e40 feat(admin): mailboxes admin (cross-org triage)
Adds GET /admin/mailboxes — paginated platform-wide mailbox list that
joins email_accounts → users → organizations so the table answers
"whose mailbox is this and where does it live" without N+1 fetches.

Search covers mailbox email / owner email / org name; status filter
defaults to active so the active surface shows first ("inactive" /
"all" both available). Provider filter speeds up "show me every Gmail
mailbox" investigations. Cursor pagination matches the rest of the
admin lists.

Frontend page surfaces warmup-on/off, send budget, and last-sync time
with red-when-never / amber-when-stale-over-24h tone so an
investigator can spot dead mailboxes fast. Mailbox email links into
the owning user's detail page; org name links into the workspace
admin so the pivot path stays one click in either direction.

Gated on AdminPermViewUsers since mailbox triage is tightly coupled to
user/org context today; a dedicated bit can be carved later if
mailbox-specific actions land.
2026-05-28 12:38:08 +02:00
Matt 705877f2ff feat(admin/ui): analytics page with daily/hourly/worker charts
Replace the AnalyticsPage stub with the full chart pack over the
existing /admin/analytics/* endpoints. Four-up trend cards on top
(users / emails / campaigns / revenue growth vs. previous period),
then a 30-day stacked bar chart for daily email volume (delivered /
replied / bounced), and a two-up row with hourly-by-today plus a
sorted worker-load list that links into each worker's detail page.

No chart library — bars are CSS divs so the admin bundle doesn't pay
for recharts/d3 for this one screen. Hover tooltips on the bars carry
the per-day breakdown.

User-growth strip lives below the email charts for symmetry with the
Overview's "platform pulse" framing.
2026-05-28 12:33:31 +02:00
Matt bd6a045751 feat(admin): outreach composer (platform mailer + reply-to + audit log)
Adds a dedicated admin path for sending platform email — distinct from
the campaign emailsend service (which sends through customer mailboxes)
so the two abuse surfaces never share code paths.

Schema (000047) adds admin_outreach_messages: every send is recorded
with sent_by, the resolved to_email, the optional reply_to, subject,
body, and a queued → sent/failed status. Failed sends keep their error
column populated for the audit log.

Extends notify.EmailNotificationService with SendOutreach so both
backends (SES + SMTP) support custom Reply-To: SES via the native
ReplyToAddresses field, SMTP via a forged Reply-To header. The
existing transactional Send() remains unchanged so no other caller is
affected.

Service (internal/app/adminoutreach) resolves recipients three ways:
to_email (raw address), to_user_id (sends to the user's account email),
or to_org_id (sends to the workspace owner). Persist-then-send-then-
mark ensures the audit row exists even if the mailer hangs, and
mark-failed captures the error string verbatim.

Routes:
  POST /admin/outreach            manage_organizations
  GET  /admin/outreach            view_organizations

Admin UI: composer with recipient mode picker (email / user_id / org_id),
configurable Reply-To (defaults to support@warmbly.com so customers can
actually reply), subject + HTML body editor, and an outreach log below
showing the last 50 sends with status badges and error details. Sidebar
entry under Accounts (Send icon).
2026-05-28 12:25:18 +02:00
Matt 93bb56a458 feat(limits): admin queue + customer request form + ToS clause
Three surfaces close the loop on the limit-increase workflow:

  - admin/dashboard/LimitRequestsPage.tsx queues every pending request
    with full context (org → users → field → current vs requested →
    +delta) and one-click approve/reject. Both actions open a review
    dialog; approve notes are optional, reject notes are required and
    surface to the customer.
  - web/settings/limits/page.tsx is the customer-facing form. Resource
    selector, requested value, reason textarea, plus a list of every
    past request with its status (pending/approved/rejected/cancelled)
    and the reviewer's notes when present. Pending rows expose a
    cancel link. Footer links to the ToS limits clause.
  - site/terms.astro grows a new section 07 ("Usage limits and
    increase requests"). Explicit: "unlimited" means no plan-tier cap
    but a product-wide hard ceiling still applies, increases are at
    Warmbly's sole discretion, and previously granted increases can be
    revoked when reputation signals deteriorate. Bumps every existing
    section heading and id from 07 onward.

Admin sidebar grows a "Limit requests" entry under Accounts (Gauge
icon). Web settings layout grows a "Limits" section under owner-only
sections.
2026-05-28 12:16:02 +02:00
Matt 7de29b0fb0 feat(admin): ban scope bitmask (schema + UI; enforcement is staged)
Add users.ban_scope INT NOT NULL DEFAULT 0 in migration 000045 so admins
can describe what a ban concretely stops (login / workspace creation /
outbound send) instead of relying on a single boolean banned_at flag
that meant "everything".

Wire flags in the BanScope enum (kept in sync with the migration) plus
a CHECK constraint guaranteeing non-negative values. Existing bans
backfill to BanScopeLogin so the historical "you can't log in"
semantics is preserved exactly — no behaviour changes silently at
deployment.

BanUserRequest gains an optional scope field, BanUser threads it through
the service to the repo write, and the UserBanDialog grows a checkbox
group with one option per flag. Reason still required; at least one
scope must be picked. Audit details now include the scope bitmask.

Runtime enforcement (refusing login when BanScopeLogin is set, etc.) is
intentionally separate from this commit — the existing codebase doesn't
yet have an active ban check anywhere, so wiring that lives across the
auth middleware, org-create handler, and emailsend service. This slice
ships the schema, the audit story, and the UI vocabulary so the
enforcement PR can land without database churn.
2026-05-28 10:00:29 +02:00
Matt e6f3708827 feat(admin/ui): plans catalog with edit dialog
Replace the PlansPage stub with the real plan catalog over /admin/plans.
The list view surfaces visibility (public/private with a colored badge),
price + discounted price, and the four limit columns most often
touched: mailboxes, campaigns, members, contacts, plus daily-email
budget.

Edit dialog covers every safely-editable field — name, price, the four
org limits, daily caps, account limit, dedicated worker count, and the
public flag. Stripe price/product IDs are intentionally read-only;
those must be managed in Stripe and flow back via webhook.
2026-05-28 09:55:59 +02:00
Matt 0c206dbfbb feat(admin/ui): enterprise inquiries pipeline
Add /enterprise page wired to /admin/enterprise/inquiries. Sales-style
pipeline: pending → contacted → converted | declined, with an inline
<select> on each row so triage is one click per inquiry rather than a
detail-page round-trip.

Each row shows company, contact, estimated volume, team size, and the
free-form notes from the marketing-site form. Pending is the default
filter so the queue surfaces first; "All" reveals historical decisions.

Added a sidebar entry under Accounts (Briefcase icon) so the inquiry
queue is one click away from the rest of the customer-facing admin.
2026-05-28 09:52:42 +02:00
Matt 3c5fb83e2b feat(admin/ui): campaigns admin with force-stop
Replace the CampaignsPage stub with a real list backed by
/admin/campaigns. Search by name, filter by status (active/paused/done/all),
inline engagement counters (contacts, sent, opens, reply %, bounce %).
Bounce rates >5% are tinted red so abuse review is one glance.

Force-stop opens a dialog requiring a reason; the reason is written to
the admin audit log. Stop is disabled on completed/draft campaigns to
prevent accidental clicks.

Org name in each row links into the workspace admin page so an
investigator can pivot from "this campaign looks bad" to "who is
sending it, on what plan, with what override history" without leaving
the admin surface.

No backend changes — every endpoint already existed.
2026-05-28 09:50:04 +02:00
Matt 7e19a306cf feat(admin/ui): warmup pools admin (health, blocked, appeals)
Replace the WarmupPage stub with the real safety surface CLAUDE.md
treats as the platform's most critical. Three layers, all wired to
existing /admin/warmup/* endpoints:

  - Health summary: four cards (total participants by state, at-risk
    count, avg spam-folder placement rate with green/amber/red tone,
    blocked count). Refetches every 30s so an investigator sees pool
    drift in near-real time.
  - Per-pool table: free + premium with total/active/blocked counts.
    Premium gets the purple badge to make the policy-isolation point
    obvious at a glance.
  - Blocked mailboxes: list with one-click unblock action. Appeals
    state surfaces inline as an amber badge when present.
  - Appeals queue: pending only by default with an approve/reject pair
    that opens a review dialog requiring notes (notes land in the
    audit log and may be shown to the appealing user).

No backend changes — every endpoint already existed.
2026-05-28 09:47:45 +02:00
Matt 1cf1523efa feat(admin/ui): user detail page with ban + rate-limit overrides
Adds /users/:id with the full preview payload (profile, orgs the user
belongs to, connected mailboxes with provider/status/warmup state, and
ban history) plus two action surfaces:

  - UserBanDialog: ban or unban from the same component, reason field
    required so the audit trail always carries it. Banning is disabled
    for admin accounts at the button level mirroring the backend's
    "cannot ban admin users" guard.
  - UserRateLimitsDialog: per-user override editor for daily emails,
    max concurrent connections, and the three websocket per-minute
    caps. Same "blank = no change, 0 = clear override, positive =
    explicit cap" convention as the org limit overrides for consistency.

Organization rows on the detail page link straight into the
organizations admin so an ops investigator can pivot user → workspace
→ workspace owner without ever leaving the admin surface.
2026-05-28 09:32:15 +02:00
Matt 7efbf7201b feat(admin/ui): users list page wired to /admin/users
Replace the UsersPage stub with a real list view. Search covers name and
email, status toggle splits active / banned / all, and an "admins only"
checkbox filters down to accounts with admin_permissions > 0. Each row
shows orgs, mailboxes, and campaign counts inline so abuse review is
one glance; admin accounts get the amber ADMIN badge from the same
visual vocabulary as the app shell.

Backend endpoints already exist — this commit only adds the typed API
client (admin/users.ts) and the page itself. Drill-in to the detail
page lands in the next commit alongside ban/unban and rate-limit
override editors.
2026-05-28 09:29:02 +02:00
Matt fe15238904 feat(admin/ui): override editor + plan/override/effective columns
Replace the two-column "Plan limit + Headroom" usage table with a
five-column view showing Used / Plan / Override / Effective / Headroom.
The override column highlights non-zero entries in the amber accent so
admin-set caps stand out from plan defaults. Headroom bars compute
against effective, not plan, so they reflect what the runtime actually
enforces.

Add OrganizationOverridesDialog — a modal launched from the detail page
that does a partial PUT against /admin/organizations/:id/overrides.
Each numeric field is blank by default (no change on submit), 0
explicitly removes that column's override, and a positive value sets a
new ceiling. The plan default and current effective value are shown
alongside each input so the admin can see what they're about to change
before saving. Notes field is required when the override is granted so
other admins can read why.

When a save succeeds the org detail query is invalidated so the
plan/override/effective columns refresh in place, and the timestamp +
notes footer below the usage table updates accordingly.
2026-05-28 08:57:45 +02:00
Matt 41bb202209 feat(admin/ui): organizations list and detail pages
Replace the OrganizationsPage stub with a real list view over
/admin/organizations: search by name/slug/owner email, status filter
(active / pending deletion / all), and inline counts (members, mailboxes,
campaigns + active) per row. Owner banned status surfaces as a red badge
on the row so abuse review is one glance.

Add OrganizationDetailPage at /organizations/:id that composes
/admin/organizations/:id with /admin/organizations/:id/members. Header
summarises owner / plan / lifecycle in three cards; body renders
usage-vs-plan-limits with green/amber/red bars (over-limit shown in red,
"no cap" for unlimited plans) and a members table tagged with role icons.

When the per-org override layer lands the usage table will gain a
"source" column (plan default vs admin override) so a 0 in an override
column reads as "no admin change" by design.
2026-05-28 08:44:18 +02:00
Matt 9d0679d0e7 chore(make): add admin/site dev shortcuts and grant-admin tooling
The admin and marketing site sit outside the compose stack, so `make app`
never started them. Add `make admin` and `make site` to launch each
workspace's dev server (Vite on 5174, Astro on 4321), and update the root
README and admin/README to point at them instead of the old "open
localhost:5174" line that implied `make app` was enough.

Also add `make grant-admin EMAIL=... [ROLE=super|support|ops|analyst]`
plus `make revoke-admin` so the first super-admin can be seeded without
hand-writing SQL. Role bitmasks mirror AdminRolePermissions in
internal/models/admin_permission.go.
2026-05-28 08:38:58 +02:00
Matthew Meszaros c886b919ce ci(admin): regenerate pnpm lockfile to match workspace overrides
admin/pnpm-workspace.yaml carries an 'overrides: picomatch: ^4.0.4'
block (added to mirror the dashboard) that was never reflected in
pnpm-lock.yaml — the lockfile was generated before the overrides were
introduced. Admin CI's pnpm install --frozen-lockfile rejected the
mismatch:

  ERR_PNPM_LOCKFILE_CONFIG_MISMATCH  Cannot proceed with the frozen
  installation. The current 'overrides' configuration doesn't match
  the value found in the lockfile.

Ran pnpm install --no-frozen-lockfile to regenerate the lockfile with
the override applied; --frozen-lockfile now passes locally.
2026-05-27 16:34:09 +00:00
Matthew Meszaros 13c4ebb7a6 rename web-admin -> admin
Shorter, cleaner path. The 'web-' prefix was redundant given the dir
sits at the repo root next to web/ and is unambiguously the admin web
app. Git tracked the rename so blame + history follow through to the
new location.

Updated README.md and docs/VENDOR_LOCKIN.md references plus the package
README header. No code changes.
2026-05-27 16:17:47 +00:00