Skip to content
6 changes: 6 additions & 0 deletions .server-changes/admin-back-office-rate-limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: feature
---

Add a "Back office" tab to `/admin` and a per-organization detail page at `/admin/back-office/orgs/:orgId`. The first action available on that page is editing the org's API rate limit: admins can save a `tokenBucket` override (refill rate, interval, max tokens) and see a plain-English preview of the resulting sustained rate and burst allowance. Writes are audit-logged via the server logger.
17 changes: 13 additions & 4 deletions apps/webapp/app/components/primitives/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type TabsProps = {
tabs: {
label: string;
to: string;
end?: boolean;
}[];
className?: string;
layoutId: string;
Expand All @@ -21,7 +22,13 @@ export function Tabs({ tabs, className, layoutId, variant = "underline" }: TabsP
return (
<TabContainer className={className} variant={variant}>
{tabs.map((tab, index) => (
<TabLink key={index} to={tab.to} layoutId={layoutId} variant={variant}>
<TabLink
key={index}
to={tab.to}
layoutId={layoutId}
variant={variant}
end={tab.end ?? true}
>
{tab.label}
</TabLink>
))}
Expand Down Expand Up @@ -62,18 +69,20 @@ export function TabLink({
children,
layoutId,
variant = "underline",
end = true,
}: {
to: string;
children: ReactNode;
layoutId: string;
variant?: Variants;
end?: boolean;
}) {
if (variant === "segmented") {
return (
<NavLink
to={to}
className="group relative flex h-full grow items-center justify-center focus-custom"
end
end={end}
>
{({ isActive, isPending }) => {
const active = isActive || isPending;
Expand Down Expand Up @@ -110,7 +119,7 @@ export function TabLink({
<NavLink
to={to}
className="group flex flex-col items-center border-r border-charcoal-700 px-2 pt-1 focus-custom first:pl-0 last:border-none"
end
end={end}
>
{({ isActive, isPending }) => {
const active = isActive || isPending;
Expand All @@ -131,7 +140,7 @@ export function TabLink({

// underline variant (default)
return (
<NavLink to={to} className="group flex flex-col items-center pt-1 focus-custom" end>
<NavLink to={to} className="group flex flex-col items-center pt-1 focus-custom" end={end}>
{({ isActive, isPending }) => {
return (
<>
Expand Down
29 changes: 29 additions & 0 deletions apps/webapp/app/routes/admin.back-office._index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson } from "remix-typedjson";
import { LinkButton } from "~/components/primitives/Buttons";
import { Header2 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { requireUser } from "~/services/session.server";

export async function loader({ request }: LoaderFunctionArgs) {
const user = await requireUser(request);
if (!user.admin) {
return redirect("/");
}
return typedjson({});
}

export default function BackOfficeIndex() {
return (
<div className="flex flex-col items-start gap-3 py-6">
<Header2>Back office</Header2>
<Paragraph variant="base" className="max-w-prose">
Back-office actions are applied to a single organization. Pick an org from the
Organizations tab to open its detail page.
</Paragraph>
<LinkButton to="/admin/orgs" variant="primary/medium">
Pick an organization
</LinkButton>
</div>
);
}
Loading
Loading