| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- using System;
- using System.Collections.ObjectModel;
- using System.Windows;
- using System.Windows.Media;
- using System.Windows.Threading;
- using CommunityToolkit.Mvvm.ComponentModel;
- using MonitorSnapshot = ivf_tl_Control.MonitorSnapshot;
- using ControlAppData = ivf_tl_Control.AppData;
- namespace ivf_tl_Operate.ViewModel
- {
- /// <summary>
- /// M5-03-3/4:只读「服务监控」页 ViewModel(需求 7 只读服务监控、需求 10 链路健康 + 不假装实时)。
- ///
- /// 纯只读:仅定时调用同进程 <see cref="ivf_tl_Control.AppData.Instance"/> 的
- /// <see cref="AppData.GetMonitorSnapshot"/> 拉取只读快照并映射到展示属性,
- /// **不持有任何可控制 control 的引用、页面无任何写控件/下发按钮**(约束 4 只读边界)。
- ///
- /// 刷新:DispatcherTimer 默认 2s(阈值/延迟具体值 [D10] 占位,留 M7 校准)。
- /// 链路健康按「最后成功通讯时间」是否过期上色:绿=正常、红=失联/过期。
- /// [M7] 各字段运行时取值、断线红/恢复绿需运行环境验证(本地不可构建/运行)。
- /// </summary>
- public partial class ServiceMonitorViewModel : ObservableObject, IDisposable
- {
- private readonly DispatcherTimer _timer;
- // [D10] 失联判定阈值占位:超过该秒数未成功通讯即视为过期/失联(留 M7 按实时指标校准)。
- private const int StaleSeconds = 30;
- public ServiceMonitorViewModel()
- {
- Houses = new ObservableCollection<HouseMonitorRowVm>();
- Refresh();
- _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
- _timer.Tick += (s, e) => Refresh();
- _timer.Start();
- }
- // —— 顶部链路健康 ——
- [ObservableProperty] private string snapshotTime;
- [ObservableProperty] private bool controlHosted;
- [ObservableProperty] private string controlHostedText;
- [ObservableProperty] private Brush controlHostedBrush;
- [ObservableProperty] private string mqttText;
- [ObservableProperty] private Brush mqttBrush;
- [ObservableProperty] private string mqttLastOk;
- [ObservableProperty] private string serverUrl;
- [ObservableProperty] private string httpText;
- [ObservableProperty] private Brush httpBrush;
- [ObservableProperty] private string httpLastOk;
- [ObservableProperty] private string kafkaText;
- [ObservableProperty] private Brush kafkaBrush;
- [ObservableProperty] private string kafkaLastOk;
- [ObservableProperty] private string uploadQueueText;
- [ObservableProperty] private Brush uploadQueueBrush;
- [ObservableProperty] private string diskText;
- [ObservableProperty] private Brush diskBrush;
- public ObservableCollection<HouseMonitorRowVm> Houses { get; }
- private static readonly Brush Green = new SolidColorBrush(Color.FromRgb(0x2E, 0xA0, 0x43));
- private static readonly Brush Red = new SolidColorBrush(Color.FromRgb(0xD0, 0x32, 0x2D));
- private static readonly Brush Gray = new SolidColorBrush(Color.FromRgb(0x95, 0x95, 0x96));
- private static readonly Brush Amber = new SolidColorBrush(Color.FromRgb(0xE0, 0x9A, 0x12));
- /// <summary>拉取只读快照并刷新展示属性。任何异常被吞掉,避免监控页崩。</summary>
- public void Refresh()
- {
- try
- {
- MonitorSnapshot snap = null;
- try { snap = ControlAppData.Instance != null ? ControlAppData.Instance.GetMonitorSnapshot() : null; } catch { snap = null; }
- if (snap == null)
- {
- ControlHosted = false;
- ControlHostedText = "后台未托管 / 取数失败";
- ControlHostedBrush = Gray;
- SnapshotTime = "—";
- return;
- }
- SnapshotTime = snap.SnapshotAt.ToString("yyyy-MM-dd HH:mm:ss");
- ControlHosted = snap.ControlHosted;
- ControlHostedText = snap.ControlHosted ? "后台服务已托管" : "后台未托管";
- ControlHostedBrush = snap.ControlHosted ? Green : Red;
- // MQTT 链路
- MqttText = snap.MqttConnected ? "已连接" : "未连接";
- MqttBrush = snap.MqttConnected ? Green : Red;
- MqttLastOk = FmtOk(snap.LastMqttOkAt);
- // 服务器 / HTTP 链路(按最后成功时间过期判定)
- ServerUrl = string.IsNullOrEmpty(snap.ServerUrl) ? "—" : snap.ServerUrl;
- HttpBrush = LinkBrush(snap.LastHttpOkAt);
- HttpText = snap.LastHttpOkAt == null ? "未知" : (IsStale(snap.LastHttpOkAt) ? "可能失联" : "正常");
- HttpLastOk = FmtOk(snap.LastHttpOkAt);
- // Kafka / 图片上传链路
- KafkaBrush = LinkBrush(snap.LastKafkaOkAt);
- KafkaText = snap.LastKafkaOkAt == null ? "未知" : (IsStale(snap.LastKafkaOkAt) ? "可能失联" : "正常");
- KafkaLastOk = FmtOk(snap.LastKafkaOkAt);
- // 上传队列(内存缓存 + 落盘待传)
- int pending = snap.ImageCacheCount + snap.PendingDiskImageCount;
- UploadQueueText = snap.ImageBacklogAlarm
- ? $"内存 {snap.ImageCacheCount} / 落盘待传 {snap.PendingDiskImageCount}(堆积告警>{snap.PendingImageAlarmThreshold})"
- : $"内存 {snap.ImageCacheCount} / 落盘待传 {snap.PendingDiskImageCount}";
- // M5-04-3:断网累积兜底——以 control 侧权威 ImageBacklogAlarm 判定红色显著提示(不静默堆积);
- // 未达阈值但有待传时琥珀提示。[D10] 阈值由 control PendingImageAlarmThreshold 提供,留 M7 校准。
- UploadQueueBrush = snap.ImageBacklogAlarm ? Red : (pending == 0 ? Green : Amber);
- // 磁盘
- if (string.IsNullOrEmpty(snap.DiskPath))
- {
- DiskText = "—";
- DiskBrush = Gray;
- }
- else if (!snap.DiskExist)
- {
- DiskText = $"{snap.DiskPath}: 盘不存在";
- DiskBrush = Red;
- }
- else
- {
- DiskText = $"{snap.DiskPath}: 剩余 {snap.DiskFreeGb:0.0} GB";
- DiskBrush = snap.DiskFreeGb < 10 ? Red : (snap.DiskFreeGb < 30 ? Amber : Green);
- }
- // 舱室
- Houses.Clear();
- foreach (var h in snap.Houses)
- {
- Houses.Add(new HouseMonitorRowVm
- {
- HouseSn = h.HouseSn,
- PortName = string.IsNullOrEmpty(h.PortName) ? "—" : h.PortName,
- RunState = string.IsNullOrEmpty(h.RunState) ? "—" : h.RunState,
- Temperature = $"{h.Temperature:0.0}",
- Pressure = $"{h.Pressure:0.0}",
- StateText = string.IsNullOrEmpty(h.HouseState) ? "—" : h.HouseState,
- StateBrush = (h.HouseState != null && h.HouseState.Contains("关")) ? Green : Red,
- ComText = string.IsNullOrEmpty(h.ComState) ? "—" : h.ComState,
- ComBrush = (h.ComState == "已连接") ? Green : Red,
- CcdText = (h.CcdState == "正常" && !h.CcdError) ? "正常" : "异常",
- CcdBrush = (h.CcdState == "正常" && !h.CcdError) ? Green : Red,
- });
- }
- }
- catch
- {
- // 监控页只读,取数异常不抛
- }
- }
- private static bool IsStale(DateTime? t) => t == null || (DateTime.Now - t.Value).TotalSeconds > StaleSeconds;
- private static Brush LinkBrush(DateTime? t)
- {
- if (t == null) return Gray; // 未知(尚未成功通讯)
- return IsStale(t) ? Red : Green; // 过期=红 / 新鲜=绿
- }
- private static string FmtOk(DateTime? t) => t == null ? "—" : t.Value.ToString("HH:mm:ss");
- public void Dispose()
- {
- try { _timer?.Stop(); } catch { }
- }
- }
- /// <summary>单舱室只读展示行。</summary>
- public class HouseMonitorRowVm
- {
- public int HouseSn { get; set; }
- public string PortName { get; set; }
- public string RunState { get; set; }
- public string Temperature { get; set; }
- public string Pressure { get; set; }
- public string StateText { get; set; }
- public Brush StateBrush { get; set; }
- public string ComText { get; set; }
- public Brush ComBrush { get; set; }
- public string CcdText { get; set; }
- public Brush CcdBrush { get; set; }
- }
- }
|