diff --git a/packages/web/src/components/layout/DmListItem.tsx b/packages/web/src/components/layout/DmListItem.tsx index 44200d8e..fe8ea415 100644 --- a/packages/web/src/components/layout/DmListItem.tsx +++ b/packages/web/src/components/layout/DmListItem.tsx @@ -3,6 +3,13 @@ import { Avatar } from '../ui/Avatar'; import { Tooltip } from '../ui/Tooltip'; import { parseFederatedUsername, isSelf } from '../../utils/identity'; import { formatDmTimestamp, formatDmPreview } from '../../utils/dmFormatters'; +import { getRejectedPeerOrigins } from '../../hooks/useWebSocket'; + +function isMemberUnreachable(homeInstance: string | null | undefined): boolean { + if (!homeInstance) return false; + const normalized = homeInstance.startsWith('http') ? homeInstance : `https://${homeInstance}`; + return getRejectedPeerOrigins().has(normalized); +} interface DmListItemProps { dm: DmChannel; @@ -152,6 +159,13 @@ export function DmListItem({ dm, isActive, isUnread, user, onSelect, onClose, on )} + {firstOther && isMemberUnreachable(firstOther.homeInstance) && ( + + + + + + )} {dm.lastMessage && ( {formatDmTimestamp(dm.lastMessage.createdAt)} diff --git a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx index 2ee20213..4b7fb946 100644 --- a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx +++ b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx @@ -14,7 +14,7 @@ function FederationGlobalSettings() { const updateInstanceSettings = useSettingsStore((s) => s.updateInstanceSettings); const addToast = useUIStore((s) => s.addToast); - const [draft, setDraft] = useState | null>(null); + const [draft, setDraft] = useState | null>(null); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(''); @@ -24,6 +24,7 @@ function FederationGlobalSettings() { federationRelayEnabled: instanceSettings.federationRelayEnabled, federationRelayTtlDays: instanceSettings.federationRelayTtlDays, defaultAutoRotateIntervalDays: instanceSettings.defaultAutoRotateIntervalDays, + autoAcceptPeering: instanceSettings.autoAcceptPeering, }); } }, [instanceSettings]); @@ -33,7 +34,8 @@ function FederationGlobalSettings() { const hasChanges = instanceSettings ? draft.federationRelayEnabled !== instanceSettings.federationRelayEnabled || draft.federationRelayTtlDays !== instanceSettings.federationRelayTtlDays || - draft.defaultAutoRotateIntervalDays !== instanceSettings.defaultAutoRotateIntervalDays + draft.defaultAutoRotateIntervalDays !== instanceSettings.defaultAutoRotateIntervalDays || + draft.autoAcceptPeering !== instanceSettings.autoAcceptPeering : false; const handleSave = async () => { @@ -55,6 +57,7 @@ function FederationGlobalSettings() { federationRelayEnabled: instanceSettings.federationRelayEnabled, federationRelayTtlDays: instanceSettings.federationRelayTtlDays, defaultAutoRotateIntervalDays: instanceSettings.defaultAutoRotateIntervalDays, + autoAcceptPeering: instanceSettings.autoAcceptPeering, }); } setSaveError(''); @@ -75,6 +78,14 @@ function FederationGlobalSettings() { setDraft({ ...draft, federationRelayEnabled: v })} /> + +
Relay TTL (days)
How long relayed messages are retained in the outbox before cleanup
@@ -161,6 +172,7 @@ function peerStatusColor(status: string): string { case 'active': return 'bg-status-online/15 text-status-online'; case 'pending': return 'bg-accent-lavender/15 text-accent-lavender'; case 'unreachable': return 'bg-accent-amber/15 text-accent-amber'; + case 'rejected': return 'bg-accent-rose/15 text-accent-rose'; case 'revoked': return 'bg-white/5 text-txt-tertiary'; default: return 'bg-white/5 text-txt-tertiary'; } @@ -171,10 +183,22 @@ function peerStatusDotColor(status: string): string { case 'active': return 'bg-status-online'; case 'pending': return 'bg-accent-lavender'; case 'unreachable': return 'bg-accent-amber'; + case 'rejected': return 'bg-accent-rose'; default: return 'bg-txt-tertiary'; } } +function peerStatusLabel(status: string): string { + switch (status) { + case 'active': return 'Active'; + case 'pending': return 'Pending'; + case 'unreachable': return 'Unreachable'; + case 'rejected': return 'Rejected (auto-peering denied)'; + case 'revoked': return 'Revoked'; + default: return status; + } +} + type PeerView = 'active' | 'revoked'; type SortBy = 'name' | 'lastSeen' | 'dateAdded' | 'failures'; type StatusFilter = 'active' | 'unreachable' | 'pending'; @@ -418,7 +442,7 @@ function PeerRow({ peer, view, expanded, onToggleExpand, onAction, defaultAutoRo
- {peer.status} + {peerStatusLabel(peer.status)} {expanded ? '▾' : '▸'}