using System.Collections.ObjectModel;
using System.ServiceProcess;
using System.Windows;
using System.Windows.Media;
using JJServiceManager.Remote;
namespace JJServiceManager
{
///
/// 초기화면의 PC 하나 (내 PC 또는 관리 대상 PC)와 그 PC의 서비스 목록
///
public sealed class HostGroup : ObservableObject
{
public const string LocalId = "local";
private string _title = "";
private string _statusText = "";
private Brush _statusBrush = Brushes2.Unknown;
private bool _isExpanded = true;
private string _emptyText = "";
/// "local" 또는 대상 PC Id
public string Id { get; init; } = LocalId;
public bool IsLocal => Id == LocalId;
/// 원격 PC일 때 접속
public RemoteClient Client { get; init; }
public ObservableCollection Rows { get; } = new ObservableCollection();
/// 제목: "내 PC (HOSTNAME)" 또는 "192.168.0.10 / SERVER01"
public string Title { get => _title; set => Set(ref _title, value); }
/// 서비스 앞에 붙는 표시 (원격: "IP/hostname")
public string Prefix => IsLocal ? "" : Title.Replace(" / ", "/");
public string StatusText { get => _statusText; set => Set(ref _statusText, value); }
public Brush StatusBrush { get => _statusBrush; set => Set(ref _statusBrush, value); }
public bool IsExpanded { get => _isExpanded; set => Set(ref _isExpanded, value); }
public string EmptyText
{
get => _emptyText;
set { if (Set(ref _emptyText, value)) OnPropertyChanged(nameof(EmptyVisibility)); }
}
public Visibility EmptyVisibility => Rows.Count == 0 && !string.IsNullOrEmpty(_emptyText) ? Visibility.Visible : Visibility.Collapsed;
/// 마지막으로 기록한 연결 실패 메시지 (같은 메시지 반복 기록 방지)
public string Tag { get; set; }
/// 원격 PC 제목 줄의 [원격관리] 버튼
public Visibility RemoteButtonVisibility => IsLocal ? Visibility.Collapsed : Visibility.Visible;
public Brush HeaderBrush => IsLocal ? Brushes2.Make("#E3F2FD") : Brushes2.Make("#F3E5F5");
public HostGroup()
{
Rows.CollectionChanged += (s, e) => OnPropertyChanged(nameof(EmptyVisibility));
}
public void TitleChanged()
{
OnPropertyChanged(nameof(Prefix));
foreach (var r in Rows) r.HostChanged();
}
public override string ToString() => Title;
}
/// 초기화면 서비스 목록의 한 줄
public sealed class ServiceRow : ObservableObject
{
private bool _isChecked;
private ServiceControllerStatus? _status;
private bool _statusKnown;
private bool _isBusy;
public ServiceEntry Entry { get; }
public HostGroup Host { get; }
public bool IsRemote => Host != null && !Host.IsLocal;
public ServiceRow(ServiceEntry entry, HostGroup host)
{
Entry = entry;
Host = host;
}
public string DisplayName => string.IsNullOrEmpty(Entry.DisplayName) ? Entry.ServiceName : Entry.DisplayName;
public string ServiceName => Entry.ServiceName;
public string ServiceNameText => string.Equals(Entry.DisplayName, Entry.ServiceName, System.StringComparison.OrdinalIgnoreCase) ? "" : $"({Entry.ServiceName})";
/// 서비스 앞에 붙는 "IP/hostname" (내 PC는 없음)
public string HostPrefix => IsRemote ? Host.Prefix : "";
public Visibility HostPrefixVisibility => IsRemote ? Visibility.Visible : Visibility.Collapsed;
/// 로그에 쓰는 이름: "[IP/hostname] [서비스]" 또는 "[서비스]"
public string LogName => IsRemote ? $"[{Host.Prefix}] [{DisplayName}]" : $"[{DisplayName}]";
public string Tooltip => IsRemote
? $"대상 PC: {Host.Title}\n서비스명: {Entry.ServiceName}"
: $"서비스명: {Entry.ServiceName}\n우클릭: 로그 경로 지정";
public bool IsChecked { get => _isChecked; set => Set(ref _isChecked, value); }
/// Start/Stop 진행 중이면 false
public bool IsIdle => !_isBusy;
public bool IsBusy
{
get => _isBusy;
set { if (Set(ref _isBusy, value)) OnPropertyChanged(nameof(IsIdle)); }
}
public ServiceControllerStatus? Status
{
get => _status;
set
{
if (_statusKnown && _status == value) return;
_status = value;
_statusKnown = true;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusText));
OnPropertyChanged(nameof(StatusBrush));
}
}
public bool StatusKnown => _statusKnown;
/// 원격 PC 연결이 끊긴 경우 등 상태를 모를 때
public void MarkUnknown()
{
if (!_statusKnown) return;
_statusKnown = false;
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(StatusText));
OnPropertyChanged(nameof(StatusBrush));
}
public string StatusText => _statusKnown ? ServiceOps.StatusText(_status) : (IsRemote ? "연결 안 됨" : "확인 중…");
public Brush StatusBrush => !_statusKnown ? Brushes2.Unknown : _status switch
{
ServiceControllerStatus.Running => Brushes2.Running,
ServiceControllerStatus.Stopped => Brushes2.Stopped,
null => Brushes2.Unknown,
ServiceControllerStatus.Paused => Brushes2.Unknown,
_ => Brushes2.Pending
};
public bool HasLog => !string.IsNullOrWhiteSpace(Entry.LogPath);
public string LogTooltip
{
get
{
if (IsRemote)
return HasLog
? "대상 PC 로그: " + (Entry.LogIsFolder ? "폴더(최신 파일) " : "") + Entry.LogPath
: "대상 PC에서 로그 경로가 지정되지 않았습니다.";
return HasLog
? (Entry.LogIsFolder ? "폴더(최신 파일): " : "파일: ") + Entry.LogPath + "\n우클릭: 변경"
: "로그 경로가 없습니다. 누르면 지정합니다. (우클릭: 변경)";
}
}
public FontWeight LogFontWeight => HasLog ? FontWeights.Bold : FontWeights.Normal;
public void LogPathChanged()
{
OnPropertyChanged(nameof(HasLog));
OnPropertyChanged(nameof(LogTooltip));
OnPropertyChanged(nameof(LogFontWeight));
}
public void HostChanged()
{
OnPropertyChanged(nameof(HostPrefix));
OnPropertyChanged(nameof(Tooltip));
OnPropertyChanged(nameof(LogName));
}
/// 원격 목록을 새로 받았을 때 이름/로그 경로 갱신
public void UpdateFromRemote(RemoteServiceInfo info)
{
bool nameChanged = Entry.DisplayName != info.DisplayName;
bool logChanged = Entry.LogPath != info.LogPath || Entry.LogIsFolder != info.LogIsFolder;
Entry.DisplayName = info.DisplayName;
Entry.LogPath = info.LogPath;
Entry.LogIsFolder = info.LogIsFolder;
if (nameChanged)
{
OnPropertyChanged(nameof(DisplayName));
OnPropertyChanged(nameof(ServiceNameText));
OnPropertyChanged(nameof(LogName));
}
if (logChanged) LogPathChanged();
Status = info.Status.HasValue ? (ServiceControllerStatus?)info.Status.Value : null;
}
}
}