mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-10 18:53:03 +02:00
Patch up radio locking and frontend contact delete behavior for bulk contact delete
This commit is contained in:
+15
-12
@@ -357,24 +357,27 @@ async def bulk_delete_contacts(request: BulkDeleteRequest) -> dict:
|
|||||||
"""Delete multiple contacts from the database (and radio if present)."""
|
"""Delete multiple contacts from the database (and radio if present)."""
|
||||||
from app.websocket import broadcast_event
|
from app.websocket import broadcast_event
|
||||||
|
|
||||||
deleted = 0
|
# Resolve all contacts first
|
||||||
|
contacts_to_delete: list[Contact] = []
|
||||||
for key in request.public_keys:
|
for key in request.public_keys:
|
||||||
normalized = key.lower()
|
contact = await ContactRepository.get_by_key(key.lower())
|
||||||
contact = await ContactRepository.get_by_key(normalized)
|
if contact:
|
||||||
if not contact:
|
contacts_to_delete.append(contact)
|
||||||
continue
|
|
||||||
|
|
||||||
if radio_manager.is_connected:
|
# Remove from radio in a single locked operation (blocks until radio is free)
|
||||||
try:
|
if radio_manager.is_connected and contacts_to_delete:
|
||||||
async with radio_manager.radio_operation(
|
try:
|
||||||
"bulk_delete_contact_from_radio", blocking=False
|
async with radio_manager.radio_operation("bulk_delete_contacts_from_radio") as mc:
|
||||||
) as mc:
|
for contact in contacts_to_delete:
|
||||||
radio_contact = mc.get_contact_by_key_prefix(contact.public_key[:12])
|
radio_contact = mc.get_contact_by_key_prefix(contact.public_key[:12])
|
||||||
if radio_contact:
|
if radio_contact:
|
||||||
await mc.commands.remove_contact(radio_contact)
|
await mc.commands.remove_contact(radio_contact)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass # Best-effort radio removal during bulk delete
|
logger.warning("Radio removal during bulk delete failed: %s", e)
|
||||||
|
|
||||||
|
# Delete from database and broadcast events
|
||||||
|
deleted = 0
|
||||||
|
for contact in contacts_to_delete:
|
||||||
await ContactRepository.delete(contact.public_key)
|
await ContactRepository.delete(contact.public_key)
|
||||||
broadcast_event("contact_deleted", {"public_key": contact.public_key})
|
broadcast_event("contact_deleted", {"public_key": contact.public_key})
|
||||||
deleted += 1
|
deleted += 1
|
||||||
|
|||||||
@@ -558,6 +558,10 @@ export function App() {
|
|||||||
onToggleBlockedKey: handleBlockKey,
|
onToggleBlockedKey: handleBlockKey,
|
||||||
onToggleBlockedName: handleBlockName,
|
onToggleBlockedName: handleBlockName,
|
||||||
contacts,
|
contacts,
|
||||||
|
onBulkDeleteContacts: (deletedKeys: string[]) => {
|
||||||
|
const keySet = new Set(deletedKeys.map((k) => k.toLowerCase()));
|
||||||
|
setContacts((prev) => prev.filter((c) => !keySet.has(c.public_key.toLowerCase())));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const crackerProps = {
|
const crackerProps = {
|
||||||
packets: rawPackets,
|
packets: rawPackets,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ interface SettingsModalBaseProps {
|
|||||||
onToggleBlockedKey?: (key: string) => void;
|
onToggleBlockedKey?: (key: string) => void;
|
||||||
onToggleBlockedName?: (name: string) => void;
|
onToggleBlockedName?: (name: string) => void;
|
||||||
contacts?: Contact[];
|
contacts?: Contact[];
|
||||||
|
onBulkDeleteContacts?: (deletedKeys: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingsModalProps = SettingsModalBaseProps &
|
export type SettingsModalProps = SettingsModalBaseProps &
|
||||||
@@ -83,6 +84,7 @@ export function SettingsModal(props: SettingsModalProps) {
|
|||||||
onToggleBlockedKey,
|
onToggleBlockedKey,
|
||||||
onToggleBlockedName,
|
onToggleBlockedName,
|
||||||
contacts,
|
contacts,
|
||||||
|
onBulkDeleteContacts,
|
||||||
} = props;
|
} = props;
|
||||||
const externalSidebarNav = props.externalSidebarNav === true;
|
const externalSidebarNav = props.externalSidebarNav === true;
|
||||||
const desktopSection = props.externalSidebarNav ? props.desktopSection : undefined;
|
const desktopSection = props.externalSidebarNav ? props.desktopSection : undefined;
|
||||||
@@ -243,6 +245,7 @@ export function SettingsModal(props: SettingsModalProps) {
|
|||||||
onToggleBlockedKey={onToggleBlockedKey}
|
onToggleBlockedKey={onToggleBlockedKey}
|
||||||
onToggleBlockedName={onToggleBlockedName}
|
onToggleBlockedName={onToggleBlockedName}
|
||||||
contacts={contacts}
|
contacts={contacts}
|
||||||
|
onBulkDeleteContacts={onBulkDeleteContacts}
|
||||||
className={sectionContentClass}
|
className={sectionContentClass}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface BulkDeleteContactsModalProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
contacts: Contact[];
|
contacts: Contact[];
|
||||||
onDeleted: () => void;
|
onDeleted: (deletedKeys: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BulkDeleteContactsModal({
|
export function BulkDeleteContactsModal({
|
||||||
@@ -133,9 +133,10 @@ export function BulkDeleteContactsModal({
|
|||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
try {
|
try {
|
||||||
const result = await api.bulkDeleteContacts([...selectedKeys]);
|
const keysToDelete = [...selectedKeys];
|
||||||
|
const result = await api.bulkDeleteContacts(keysToDelete);
|
||||||
toast.success(`Deleted ${result.deleted} contact${result.deleted === 1 ? '' : 's'}`);
|
toast.success(`Deleted ${result.deleted} contact${result.deleted === 1 ? '' : 's'}`);
|
||||||
onDeleted();
|
onDeleted(keysToDelete);
|
||||||
resetAndClose();
|
resetAndClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Bulk delete failed:', err);
|
console.error('Bulk delete failed:', err);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export function SettingsDatabaseSection({
|
|||||||
onToggleBlockedKey,
|
onToggleBlockedKey,
|
||||||
onToggleBlockedName,
|
onToggleBlockedName,
|
||||||
contacts = [],
|
contacts = [],
|
||||||
|
onBulkDeleteContacts,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
appSettings: AppSettings;
|
appSettings: AppSettings;
|
||||||
@@ -30,6 +31,7 @@ export function SettingsDatabaseSection({
|
|||||||
onToggleBlockedKey?: (key: string) => void;
|
onToggleBlockedKey?: (key: string) => void;
|
||||||
onToggleBlockedName?: (name: string) => void;
|
onToggleBlockedName?: (name: string) => void;
|
||||||
contacts?: Contact[];
|
contacts?: Contact[];
|
||||||
|
onBulkDeleteContacts?: (deletedKeys: string[]) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const [retentionDays, setRetentionDays] = useState('14');
|
const [retentionDays, setRetentionDays] = useState('14');
|
||||||
@@ -297,7 +299,7 @@ export function SettingsDatabaseSection({
|
|||||||
open={bulkDeleteOpen}
|
open={bulkDeleteOpen}
|
||||||
onClose={() => setBulkDeleteOpen(false)}
|
onClose={() => setBulkDeleteOpen(false)}
|
||||||
contacts={contacts}
|
contacts={contacts}
|
||||||
onDeleted={() => {}}
|
onDeleted={(keys) => onBulkDeleteContacts?.(keys)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user