feat: add domain rules management feature

- Introduced a new DomainRulesPage component for managing custom and global equivalent domains.
- Updated AppMainRoutes to include a route for domain rules.
- Added API functions to fetch and save domain rules.
- Enhanced localization with new strings for domain rules in multiple languages.
- Updated styles for the new domain rules interface and ensured responsiveness.
- Added types for domain rules in the TypeScript definitions.
This commit is contained in:
shuaiplus
2026-05-06 00:33:09 +08:00
parent 246c73a3d3
commit 0a001bebcc
32 changed files with 2045 additions and 32 deletions
+34 -2
View File
@@ -1,19 +1,20 @@
import { lazy, Suspense } from 'preact/compat';
import { useEffect } from 'preact/hooks';
import { Link, Route, Switch } from 'wouter';
import { ArrowUpDown, Cloud, LogOut, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact';
import { ArrowUpDown, Cloud, Globe2, LogOut, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact';
import type { ImportAttachmentFile, ImportResultSummary } from '@/components/ImportPage';
import LoadingState from '@/components/LoadingState';
import type { AdminBackupImportResponse, AdminBackupRunResponse, AdminBackupSettings, RemoteBackupBrowserResponse } from '@/lib/api/backup';
import type { CiphersImportPayload } from '@/lib/api/vault';
import { t } from '@/lib/i18n';
import type { AdminInvite, AdminUser, AuthorizedDevice, Cipher, Folder as VaultFolder, Profile, Send, SendDraft, SessionState, VaultDraft } from '@/lib/types';
import type { AdminInvite, AdminUser, AuthorizedDevice, Cipher, CustomEquivalentDomain, DomainRules, Folder as VaultFolder, Profile, Send, SendDraft, SessionState, VaultDraft } from '@/lib/types';
import type { ExportRequest } from '@/lib/export-formats';
const VaultPage = lazy(() => import('@/components/VaultPage'));
const SendsPage = lazy(() => import('@/components/SendsPage'));
const TotpCodesPage = lazy(() => import('@/components/TotpCodesPage'));
const SettingsPage = lazy(() => import('@/components/SettingsPage'));
const DomainRulesPage = lazy(() => import('@/components/DomainRulesPage'));
const SecurityDevicesPage = lazy(() => import('@/components/SecurityDevicesPage'));
const AdminPage = lazy(() => import('@/components/AdminPage'));
const BackupCenterPage = lazy(() => import('@/components/BackupCenterPage'));
@@ -56,6 +57,9 @@ export interface AppMainRoutesProps {
authorizedDevices: AuthorizedDevice[];
authorizedDevicesLoading: boolean;
authorizedDevicesError: string;
domainRules: DomainRules | null;
domainRulesLoading: boolean;
domainRulesError: string;
onNavigate: (path: string) => void;
onLogout: () => void;
onNotify: (type: 'success' | 'error' | 'warning', text: string) => void;
@@ -108,6 +112,8 @@ export interface AppMainRoutesProps {
onLockTimeoutChange: (minutes: 0 | 1 | 5 | 15 | 30) => void;
onSessionTimeoutActionChange: (action: 'lock' | 'logout') => void;
onRefreshAuthorizedDevices: () => Promise<void>;
onRefreshDomainRules: () => void;
onSaveDomainRules: (customEquivalentDomains: CustomEquivalentDomain[], excludedGlobalEquivalentDomains: number[]) => Promise<void>;
onRenameAuthorizedDevice: (device: AuthorizedDevice, name: string) => Promise<void>;
onRevokeDeviceTrust: (device: AuthorizedDevice) => void;
onRemoveDevice: (device: AuthorizedDevice) => void;
@@ -268,6 +274,10 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
<Shield size={18} />
<span>{t('nav_device_management')}</span>
</Link>
<Link href="/settings/domain-rules" className="mobile-settings-link">
<Globe2 size={18} />
<span>{t('nav_domain_rules')}</span>
</Link>
<Link href={props.importRoute} className="mobile-settings-link">
<ArrowUpDown size={18} />
<span>{t('nav_import_export')}</span>
@@ -319,6 +329,28 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
</Suspense>
</div>
</Route>
<Route path="/settings/domain-rules">
<div className="stack domain-rules-route">
{props.mobileLayout && (
<div className="mobile-settings-subhead">
<button type="button" className="btn btn-secondary small mobile-settings-back" onClick={() => props.onNavigate(props.settingsHomeRoute)}>
<span className="btn-icon" aria-hidden="true">{"<"}</span>
{t('txt_back')}
</button>
</div>
)}
<Suspense fallback={<RouteContentFallback />}>
<DomainRulesPage
rules={props.domainRules}
loading={props.domainRulesLoading}
error={props.domainRulesError}
onRefresh={props.onRefreshDomainRules}
onSave={props.onSaveDomainRules}
onNotify={props.onNotify}
/>
</Suspense>
</div>
</Route>
<Route path="/admin">
<div className="stack">
{props.mobileLayout && (