Settings › Overview offers the limits and directory guides too #49
No files matched your search
@@ -15,6 +15,35 @@ import { cn } from '@/lib/utils';
|
||||
|
||||
export const DIRECTORY_WIZARD_VIEW = 'Wizard/directory';
|
||||
|
||||
/** "Guided or manual?" for connecting a directory. Manual opens Directories unless told otherwise. */
|
||||
export function DirectoryLaunchChoice({
|
||||
open,
|
||||
onOpenChange,
|
||||
onManual,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onManual?: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const section = useSchemaStore((s) => s.viewToSection['x:Directory']) ?? 'Settings';
|
||||
return (
|
||||
<LaunchChoice
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t('dirCard.chooseTitle', 'Connect a sign-in directory')}
|
||||
guidedHint={t(
|
||||
'dirCard.guidedHint',
|
||||
'Pick your directory, fill in the connection, test a person, then choose domains. Nothing moves until the test passes.',
|
||||
)}
|
||||
manualHint={t('dirCard.manualHint', 'Create the directory and set each domain’s directory yourself.')}
|
||||
onGuided={() => navigate(`/${section}/${DIRECTORY_WIZARD_VIEW}`)}
|
||||
onManual={onManual ?? (() => navigate(`/${section}/x:Directory`))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* inbuxa: at the top of Directories and Authentication, an invitation to
|
||||
* connect a directory with the guide (settings-reorg, first wave). Manual
|
||||
@@ -22,8 +51,6 @@ export const DIRECTORY_WIZARD_VIEW = 'Wizard/directory';
|
||||
*/
|
||||
export function DirectorySetupCard({ className }: { className?: string }) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const section = useSchemaStore((s) => s.viewToSection['x:Directory']) ?? 'Settings';
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div
|
||||
@@ -45,18 +72,7 @@ export function DirectorySetupCard({ className }: { className?: string }) {
|
||||
<Button type="button" onClick={() => setOpen(true)}>
|
||||
{t('dirCard.start', 'Set it up')}
|
||||
</Button>
|
||||
<LaunchChoice
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={t('dirCard.chooseTitle', 'Connect a sign-in directory')}
|
||||
guidedHint={t(
|
||||
'dirCard.guidedHint',
|
||||
'Pick your directory, fill in the connection, test a person, then choose domains. Nothing moves until the test passes.',
|
||||
)}
|
||||
manualHint={t('dirCard.manualHint', 'Create the directory and set each domain’s directory yourself.')}
|
||||
onGuided={() => navigate(`/${section}/${DIRECTORY_WIZARD_VIEW}`)}
|
||||
onManual={() => undefined}
|
||||
/>
|
||||
<DirectoryLaunchChoice open={open} onOpenChange={setOpen} onManual={() => undefined} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -22,10 +22,37 @@ import { describe, type LimitObject, type RuleRecord } from './rules';
|
||||
|
||||
export const LIMITS_WIZARD_VIEW = 'Wizard/limits';
|
||||
|
||||
export function LimitsSummary({ object }: { object: LimitObject }) {
|
||||
/** "Guided or manual?" for limits. Manual opens the incoming limits unless told otherwise. */
|
||||
export function LimitsLaunchChoice({
|
||||
open,
|
||||
onOpenChange,
|
||||
onManual,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onManual?: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const section = useSchemaStore((s) => s.viewToSection[object]) ?? 'Settings';
|
||||
const section = useSchemaStore((s) => s.viewToSection['x:MtaInboundThrottle']) ?? 'Settings';
|
||||
return (
|
||||
<LaunchChoice
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t('limitsSummary.chooseTitle', 'Sending and receiving limits')}
|
||||
guidedHint={t(
|
||||
'limitsSummary.guidedHint',
|
||||
'Say who uses this server and get five sensible limits, each shown as a sentence you can adjust.',
|
||||
)}
|
||||
manualHint={t('limitsSummary.manualHint', 'Add and edit each rule yourself, with every option.')}
|
||||
onGuided={() => navigate(`/${section}/${LIMITS_WIZARD_VIEW}`)}
|
||||
onManual={onManual ?? (() => navigate(`/${section}/x:MtaInboundThrottle`))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function LimitsSummary({ object }: { object: LimitObject }) {
|
||||
const { t } = useTranslation();
|
||||
const [rules, setRules] = useState<RuleRecord[] | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -68,18 +95,7 @@ export function LimitsSummary({ object }: { object: LimitObject }) {
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<LaunchChoice
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={t('limitsSummary.chooseTitle', 'Sending and receiving limits')}
|
||||
guidedHint={t(
|
||||
'limitsSummary.guidedHint',
|
||||
'Say who uses this server and get five sensible limits, each shown as a sentence you can adjust.',
|
||||
)}
|
||||
manualHint={t('limitsSummary.manualHint', 'Add and edit each rule yourself, with every option.')}
|
||||
onGuided={() => navigate(`/${section}/${LIMITS_WIZARD_VIEW}`)}
|
||||
onManual={() => undefined}
|
||||
/>
|
||||
<LimitsLaunchChoice open={open} onOpenChange={setOpen} onManual={() => undefined} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import { SETTINGS_CATEGORIES, SETTINGS_LAYOUT_NAME } from '@/lib/settingsLayout'
|
||||
import { resolveObject } from '@/lib/schemaResolver';
|
||||
import { countChanged, singletonPages, type SettingsPageRef } from './changedFromDefault';
|
||||
import { SENDING_WIZARD_VIEW, SendingLaunchChoice } from '@/features/sending/SendingSetupCard';
|
||||
import { DIRECTORY_WIZARD_VIEW, DirectoryLaunchChoice } from '@/features/directory/DirectorySetupCard';
|
||||
import { LIMITS_WIZARD_VIEW, LimitsLaunchChoice } from '@/features/limits/LimitsSummary';
|
||||
import { CERTIFICATE_WIZARD_VIEW, CertificateLaunchChoice } from '@/features/certificates/CertificateSetupCard';
|
||||
|
||||
/** Calls per JMAP request; the protocol's own floor, so any server takes it. */
|
||||
@@ -50,6 +52,31 @@ async function anyExist(objectName: string): Promise<boolean> {
|
||||
|
||||
/** Each guided setup registers here as it lands. */
|
||||
const GUIDED_SETUPS: GuidedSetup[] = [
|
||||
{
|
||||
id: 'limits',
|
||||
title: 'Sending and receiving limits',
|
||||
blurb: 'Rate limits that suit who uses this server, so one stolen password can’t send without end.',
|
||||
to: { choice: (props) => <LimitsLaunchChoice {...props} /> },
|
||||
gate: LIMITS_WIZARD_VIEW,
|
||||
isDone: async () => {
|
||||
const [res] = await jmapRequest([
|
||||
[
|
||||
'x:MtaInboundThrottle/get',
|
||||
{ accountId: getAccountId('x:MtaInboundThrottle'), ids: null, properties: ['id'] },
|
||||
'0',
|
||||
],
|
||||
]);
|
||||
return ((res?.[1]?.list as unknown[] | undefined)?.length ?? 0) > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'directory',
|
||||
title: 'Connect a sign-in directory',
|
||||
blurb:
|
||||
'Let people sign in with Active Directory, LDAP or an OpenID Connect provider, tested before anything moves.',
|
||||
to: { choice: (props) => <DirectoryLaunchChoice {...props} /> },
|
||||
gate: DIRECTORY_WIZARD_VIEW,
|
||||
},
|
||||
{
|
||||
id: 'certificates',
|
||||
title: 'Certificates, automatically',
|
||||
|
||||
Reference in new issue
Block a user