(ip, hostname, mac, is_alive)
| 3029 | try: |
| 3030 | with scanner._lock: |
| 3031 | findings = [f.to_dict() for f in scanner.scan_results.get(scan_id, [])] |
| 3032 | except Exception as e: |
| 3033 | return jsonify({'success': False, 'error': str(e)}), 500 |
| 3034 | return jsonify({'success': True, 'findings': findings}) |
| 3035 | |
| 3036 | |
| 3037 | @app.route('/api/mesh/scan/cancel/<scan_id>', methods=['POST']) |
| 3038 | def mesh_scan_cancel_remote(scan_id): |
| 3039 | """Cancel a delegated scan running on this unit. Peer-write allowlisted.""" |
| 3040 | scanner = get_advanced_vuln_scanner() |
| 3041 | if not scanner: |
| 3042 | return jsonify({'success': False, 'error': 'Scanner not available'}), 503 |
| 3043 | return jsonify({'success': bool(scanner.cancel_scan(scan_id))}) |
| 3044 | |
| 3045 | |
| 3046 | def _mesh_poll_once(): |
| 3047 | """Refresh cached peer health. Safe to call when Tailscale is absent.""" |
| 3048 | global _mesh_last_poll |
| 3049 | if not _mesh_enabled(): |
| 3050 | return |
| 3051 | state = mesh_manager.status() |
| 3052 | if not state.get('available'): |
| 3053 | return |
| 3054 | try: |
| 3055 | timeout = max(1, int(shared_data.config.get('mesh_poll_timeout', 6))) |
| 3056 | except (TypeError, ValueError): |
| 3057 | timeout = 6 |
| 3058 | # Only tagged peers are Ragnars. Polling every tailnet device would mean |
| 3059 | # hammering the operator's laptop and phone with HTTP requests. |
| 3060 | tag = _mesh_tag() |
| 3061 | peers = [p for p in state.get('peers', []) if tag in p.get('tags', [])] |
| 3062 | # Poll every tagged unit, even ones Tailscale currently marks offline: its |
| 3063 | # Online flag lags real reachability, and skipping "offline" peers is what |
| 3064 | # silently stopped data sharing once units went idle. The poll doubles as |
| 3065 | # the keepalive that flips them back online. |
| 3066 | try: |
| 3067 | results = mesh_manager.poll_mesh(peers, port=_mesh_node_port(), |
| 3068 | timeout=timeout, include_offline=True) |
| 3069 | except TypeError: |
| 3070 | # Defends against a partial update: a stale mesh_manager whose poll_mesh |
| 3071 | # predates the include_offline argument would otherwise raise here and |
| 3072 | # kill ALL polling silently (every peer stuck on "Not polled") while the |
| 3073 | # rest of Ragnar looks current. Fall back to the old signature so peers |
| 3074 | # are still polled, and say loudly that the box needs a clean update. |
| 3075 | logger.warning("[mesh] poll_mesh() rejected include_offline — stale " |
| 3076 | "mesh_manager.py (partial update). Polling online peers " |
| 3077 | "only; run a clean 'git pull' + restart on this unit.") |
| 3078 | results = mesh_manager.poll_mesh(peers, port=_mesh_node_port(), timeout=timeout) |
| 3079 | with _mesh_lock: |
| 3080 | _mesh_peer_health.clear() |
| 3081 | _mesh_peer_health.update(results) |
| 3082 | _mesh_last_poll = time.time() |
| 3083 | reachable = sum(1 for r in results.values() if r.get('reachable')) |
| 3084 | logger.info(f"[mesh] polled {len(results)}/{len(peers)} tagged peer(s), " |
| 3085 | f"{reachable} reachable") |
| 3086 | _mesh_poll_state['last_ok_at'] = time.time() |
| 3087 | _mesh_poll_state['last_error'] = '' |
| 3088 | _mesh_poll_state['last_summary'] = (f"polled {len(results)}/{len(peers)} " |
no test coverage detected