| 1778 | _net_integrity_check_once() |
| 1779 | except Exception as exc: |
| 1780 | logger.debug(f"[net-integrity] monitor cycle failed: {exc}") |
| 1781 | try: |
| 1782 | interval = max(1, int(shared_data.config.get('net_integrity_interval_min', 5))) |
| 1783 | except (TypeError, ValueError): |
| 1784 | interval = 5 |
| 1785 | # Sleep the interval in short steps so a config toggle / shutdown is seen. |
| 1786 | for _ in range(interval * 60 // 5): |
| 1787 | if getattr(shared_data, 'webapp_should_exit', False): |
| 1788 | return |
| 1789 | if not shared_data.config.get('net_integrity_monitor_enabled', False): |
| 1790 | break |
| 1791 | _time.sleep(5) |
| 1792 | |
| 1793 | |
| 1794 | @app.route('/api/net/integrity', methods=['GET']) |
| 1795 | def net_integrity_status(): |
| 1796 | """Last verdict from the passive network-integrity monitor (for the |
| 1797 | dashboard chip). Reflects config even when the monitor hasn't run yet.""" |
| 1798 | with _net_integrity_lock: |
| 1799 | state = dict(_net_integrity_state) |
| 1800 | state['monitor_enabled'] = bool(shared_data.config.get('net_integrity_monitor_enabled', False)) |
| 1801 | state['interval_min'] = shared_data.config.get('net_integrity_interval_min', 5) |
| 1802 | state['extended_enabled'] = bool(shared_data.config.get('net_integrity_extended_enabled', True)) |
| 1803 | state['batch_size'] = shared_data.config.get('net_integrity_batch_size', 3) |
| 1804 | state['interface'] = shared_data.config.get('net_integrity_interface', '') or '' |
| 1805 | return jsonify({'success': True, **state}) |
| 1806 | |
| 1807 | |
| 1808 | # ========================================================================== |
| 1809 | # Watchtower — unified alert pane for the standalone passive watchers. |
| 1810 | # The deep daemons (arp_guard, ndpwatch, wifiwatch, certwatch, snmpwatch, |
| 1811 | # isiswatch, igmpwatch) each write their own JSON-lines log; Watchtower tails |