mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-12 03:32:56 +02:00
feat(contacts): add push-to-device and move-to-cache operations
Enable moving contacts between device and cache directly from the Existing Contacts UI: - "To device" button on cache-only contacts (pushes to device) - "To cache" button on device contacts (removes from device, keeps in DB) This helps manage the 350-contact device limit by offloading inactive contacts to cache and restoring them when needed. - Add DeviceManager.push_to_device() and move_to_cache() methods - Add API endpoints: POST /contacts/<pk>/push-to-device, move-to-cache - Add UI buttons with confirm dialogs in contacts.js Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1465,6 +1465,52 @@ class DeviceManager:
|
||||
logger.error(f"Failed to delete cached contact: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
def push_to_device(self, pubkey: str) -> Dict:
|
||||
"""Push a cache-only contact to the device."""
|
||||
if not self.is_connected:
|
||||
return {'success': False, 'error': 'Device not connected'}
|
||||
|
||||
# Already on device?
|
||||
if self.mc.contacts and pubkey in self.mc.contacts:
|
||||
return {'success': False, 'error': 'Contact is already on device'}
|
||||
|
||||
db_contact = self.db.get_contact(pubkey)
|
||||
if not db_contact:
|
||||
return {'success': False, 'error': 'Contact not found in cache'}
|
||||
|
||||
name = db_contact.get('name', '')
|
||||
contact_type = db_contact.get('type', 1)
|
||||
if contact_type == 0:
|
||||
contact_type = 1 # NONE → COM
|
||||
|
||||
return self.add_contact_manual(
|
||||
name=name,
|
||||
public_key=pubkey,
|
||||
contact_type=contact_type,
|
||||
)
|
||||
|
||||
def move_to_cache(self, pubkey: str) -> Dict:
|
||||
"""Move a device contact to cache (remove from device, keep in DB)."""
|
||||
if not self.is_connected:
|
||||
return {'success': False, 'error': 'Device not connected'}
|
||||
|
||||
if not self.mc.contacts or pubkey not in self.mc.contacts:
|
||||
return {'success': False, 'error': 'Contact not on device'}
|
||||
|
||||
contact = self.mc.contacts[pubkey]
|
||||
name = contact.get('adv_name', contact.get('name', ''))
|
||||
|
||||
try:
|
||||
self.execute(self.mc.commands.remove_contact(pubkey))
|
||||
self.db.delete_contact(pubkey) # soft-delete: sets source='advert'
|
||||
if self.mc.contacts and pubkey in self.mc.contacts:
|
||||
del self.mc.contacts[pubkey]
|
||||
logger.info(f"Moved to cache: {name} ({pubkey[:12]}...)")
|
||||
return {'success': True, 'message': f'{name} moved to cache'}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to move contact to cache: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
def reset_path(self, pubkey: str) -> Dict:
|
||||
"""Reset path to a contact."""
|
||||
if not self.is_connected:
|
||||
|
||||
@@ -2738,6 +2738,38 @@ def delete_cached_contact_api():
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@api_bp.route('/contacts/<public_key>/push-to-device', methods=['POST'])
|
||||
def push_contact_to_device(public_key):
|
||||
"""Push a cache-only contact to the device."""
|
||||
try:
|
||||
dm = _get_dm()
|
||||
if not dm:
|
||||
return jsonify({'success': False, 'error': 'Device manager unavailable'}), 500
|
||||
|
||||
result = dm.push_to_device(public_key.strip().lower())
|
||||
status = 200 if result['success'] else 400
|
||||
return jsonify(result), status
|
||||
except Exception as e:
|
||||
logger.error(f"Error pushing contact to device: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@api_bp.route('/contacts/<public_key>/move-to-cache', methods=['POST'])
|
||||
def move_contact_to_cache(public_key):
|
||||
"""Move a device contact to cache (remove from device, keep in DB)."""
|
||||
try:
|
||||
dm = _get_dm()
|
||||
if not dm:
|
||||
return jsonify({'success': False, 'error': 'Device manager unavailable'}), 500
|
||||
|
||||
result = dm.move_to_cache(public_key.strip().lower())
|
||||
status = 200 if result['success'] else 400
|
||||
return jsonify(result), status
|
||||
except Exception as e:
|
||||
logger.error(f"Error moving contact to cache: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@api_bp.route('/contacts/protected', methods=['GET'])
|
||||
def get_protected_contacts_api():
|
||||
"""
|
||||
|
||||
@@ -2290,7 +2290,7 @@ function createExistingContactCard(contact, index) {
|
||||
actionsDiv.appendChild(mapBtn);
|
||||
}
|
||||
|
||||
// Protect & Delete buttons (only for device contacts)
|
||||
// Protect, Move to cache & Delete buttons (only for device contacts)
|
||||
if (contact.on_device !== false) {
|
||||
const protectBtn = document.createElement('button');
|
||||
protectBtn.className = isProtected ? 'btn btn-sm btn-warning' : 'btn btn-sm btn-outline-warning';
|
||||
@@ -2300,6 +2300,17 @@ function createExistingContactCard(contact, index) {
|
||||
protectBtn.onclick = () => toggleContactProtection(contact.public_key, protectBtn);
|
||||
actionsDiv.appendChild(protectBtn);
|
||||
|
||||
const moveToCacheBtn = document.createElement('button');
|
||||
moveToCacheBtn.className = 'btn btn-sm btn-outline-info';
|
||||
moveToCacheBtn.innerHTML = '<i class="bi bi-cloud-arrow-down"></i> <span class="btn-label">To cache</span>';
|
||||
moveToCacheBtn.title = 'Remove from device, keep in cache';
|
||||
moveToCacheBtn.onclick = () => moveContactToCache(contact);
|
||||
moveToCacheBtn.disabled = isProtected;
|
||||
if (isProtected) {
|
||||
moveToCacheBtn.title = 'Cannot move protected contact';
|
||||
}
|
||||
actionsDiv.appendChild(moveToCacheBtn);
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'btn btn-sm btn-outline-danger';
|
||||
deleteBtn.innerHTML = '<i class="bi bi-trash"></i> <span class="btn-label">Delete</span>';
|
||||
@@ -2311,8 +2322,15 @@ function createExistingContactCard(contact, index) {
|
||||
actionsDiv.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
// Delete button for cache-only contacts
|
||||
// Push to device & Delete buttons for cache-only contacts
|
||||
if (contact.on_device === false) {
|
||||
const pushToDeviceBtn = document.createElement('button');
|
||||
pushToDeviceBtn.className = 'btn btn-sm btn-outline-success';
|
||||
pushToDeviceBtn.innerHTML = '<i class="bi bi-cpu"></i> <span class="btn-label">To device</span>';
|
||||
pushToDeviceBtn.title = 'Add this contact to the device';
|
||||
pushToDeviceBtn.onclick = () => pushContactToDevice(contact);
|
||||
actionsDiv.appendChild(pushToDeviceBtn);
|
||||
|
||||
const deleteCacheBtn = document.createElement('button');
|
||||
deleteCacheBtn.className = 'btn btn-sm btn-outline-danger';
|
||||
deleteCacheBtn.innerHTML = '<i class="bi bi-trash"></i> <span class="btn-label">Delete</span>';
|
||||
@@ -2493,3 +2511,47 @@ async function confirmDelete() {
|
||||
contactToDelete = null;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Push to Device / Move to Cache
|
||||
// =============================================================================
|
||||
|
||||
async function pushContactToDevice(contact) {
|
||||
if (!confirm(`Push "${contact.name}" to device?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/contacts/${contact.public_key}/push-to-device`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast(data.message || `${contact.name} pushed to device`, 'success');
|
||||
setTimeout(() => loadExistingContacts(), 500);
|
||||
} else {
|
||||
showToast(data.error || 'Failed to push contact', 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Network error: ' + error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function moveContactToCache(contact) {
|
||||
if (!confirm(`Move "${contact.name}" from device to cache?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/contacts/${contact.public_key}/move-to-cache`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast(data.message || `${contact.name} moved to cache`, 'success');
|
||||
setTimeout(() => loadExistingContacts(), 500);
|
||||
} else {
|
||||
showToast(data.error || 'Failed to move contact', 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Network error: ' + error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user