From 84cafc48a2be3c9a6acb7d6154ce6397c9773b9f Mon Sep 17 00:00:00 2001 From: diadmin Date: Tue, 27 Oct 2026 01:56:10 +0000 Subject: [PATCH] =?UTF-8?q?JJServiceManager=201.0.0:=20Windows=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B8=B0=EB=8F=99/=EC=A2=85=EB=A3=8C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=9E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 초기화면: 서비스 Start/Stop/Status/Log, 체크 항목 일괄 제어, 상태 자동 갱신 - 추가 선택 화면: 전체 서비스 목록, 열 정렬, 검색, 등록된 서비스 표시 - 로그: 화면클리어/로그 클리어/복사, 서비스 로그 파일 실시간 보기(개별 창/탭/좌우 분할) - 설정: 시작시 기동, 전역 단축키, Esc 동작, Windows 종료 버튼 표시 - 트레이, 관리자 권한 실행, 중복 실행 방지 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013U497kUNL8ArhV7jkzCpKa --- .gitignore | 7 + Core/LogPanel.cs | 343 ++++++++++++++++ Core/LogTailer.cs | 413 +++++++++++++++++++ Core/LogWindows.cs | 496 +++++++++++++++++++++++ Core/Models.cs | 173 ++++++++ Core/Services.cs | 271 +++++++++++++ Core/SystemIntegration.cs | 263 +++++++++++++ Core/Ui.cs | 114 ++++++ JJServiceManager.csproj | 32 ++ Program.cs | 93 +++++ README.md | 59 +++ Views/LogPanel.xaml | 54 +++ Views/MainWindow.xaml | 98 +++++ Views/ServicePicker.xaml | 95 +++++ Views/Settings.xaml | 74 ++++ Views/Styles.xaml | 67 ++++ Windows/MainWindow.cs | 700 +++++++++++++++++++++++++++++++++ Windows/ServicePickerWindow.cs | 152 +++++++ Windows/ServiceRow.cs | 73 ++++ Windows/SettingsWindow.cs | 118 ++++++ app.ico | Bin 0 -> 25702 bytes app.manifest | 23 ++ build.bat | 23 ++ 23 files changed, 3741 insertions(+) create mode 100644 .gitignore create mode 100644 Core/LogPanel.cs create mode 100644 Core/LogTailer.cs create mode 100644 Core/LogWindows.cs create mode 100644 Core/Models.cs create mode 100644 Core/Services.cs create mode 100644 Core/SystemIntegration.cs create mode 100644 Core/Ui.cs create mode 100644 JJServiceManager.csproj create mode 100644 Program.cs create mode 100644 README.md create mode 100644 Views/LogPanel.xaml create mode 100644 Views/MainWindow.xaml create mode 100644 Views/ServicePicker.xaml create mode 100644 Views/Settings.xaml create mode 100644 Views/Styles.xaml create mode 100644 Windows/MainWindow.cs create mode 100644 Windows/ServicePickerWindow.cs create mode 100644 Windows/ServiceRow.cs create mode 100644 Windows/SettingsWindow.cs create mode 100644 app.ico create mode 100644 app.manifest create mode 100644 build.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cd3d20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +publish/ +dist/ +.vs/ +*.user +*.suo diff --git a/Core/LogPanel.cs b/Core/LogPanel.cs new file mode 100644 index 0000000..a26f4bc --- /dev/null +++ b/Core/LogPanel.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; + +namespace JJServiceManager +{ + public enum LogLevel { Info, Success, Warning, Error, Notice } + + /// 로그 한 줄 + public class LogLine : ObservableObject + { + private bool _isChecked; + private double _fillerHeight; + + public int No { get; init; } + public DateTime Time { get; init; } + public string Text { get; init; } = ""; + public LogLevel Level { get; init; } + public bool ShowTime { get; init; } + public bool IsFiller { get; init; } + + public bool IsChecked { get => _isChecked; set => Set(ref _isChecked, value); } + + public double FillerHeight + { + get => _fillerHeight; + set => Set(ref _fillerHeight, Math.Max(0, value)); + } + + public string NoText => IsFiller ? "" : No.ToString(); + public string Display => IsFiller ? "" : (ShowTime ? $"[{Time:HH:mm:ss}] {Text}" : Text); + public Visibility FillerVisibility => IsFiller ? Visibility.Visible : Visibility.Collapsed; + public Visibility LineVisibility => IsFiller ? Visibility.Collapsed : Visibility.Visible; + + public Brush Foreground => Level switch + { + LogLevel.Success => Brushes2.Running, + LogLevel.Warning => Brushes2.Pending, + LogLevel.Error => Brushes2.Stopped, + LogLevel.Notice => LogPanel.NoticeBrush, + _ => LogPanel.NormalBrush + }; + + /// 복사할 때의 텍스트 + public string CopyText => ShowTime ? $"[{Time:yyyy-MM-dd HH:mm:ss}] {Text}" : Text; + } + + /// 앞쪽 여러 줄을 한 번에 지울 수 있는 컬렉션 (변경 알림 1회) + public sealed class LogCollection : ObservableCollection + { + public void RemoveFirst(int count) + { + if (count <= 0) return; + CheckReentrancy(); + var list = (List)Items; + list.RemoveRange(0, Math.Min(count, list.Count)); + OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Count")); + OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs( + System.Collections.Specialized.NotifyCollectionChangedAction.Reset)); + } + } + + /// + /// 번호 + 체크박스가 있는 로그 목록. + /// - 화면클리어: 새 로그를 화면 맨 위부터 출력, 위로 스크롤하면 이전 로그가 보임, 번호는 이어짐 + /// - 로그 클리어: 화면의 로그를 모두 지우고 번호를 1번부터 다시 시작 + /// - 파일은 절대 지우지 않음 + /// + public class LogPanel : ContentControl + { + public static readonly SolidColorBrush NormalBrush = Brushes2.Make("#212121"); + public static readonly SolidColorBrush NoticeBrush = Brushes2.Make("#1565C0"); + + private const double LineHeight = 22; + + private readonly LogCollection _lines = new LogCollection(); + private readonly ListBox _list; + private readonly TextBlock _info; + private readonly DispatcherTimer _infoTimer; + private ScrollViewer _scroll; + private LogLine _filler; + private int _fillerNewLines; + private int _nextNo = 1; + private bool _scrollPending; + + /// 줄 앞에 시각 표시 여부 (작업 로그 = true, 파일 로그 = false) + public bool ShowTimestamp { get; set; } = true; + + /// 화면에 유지할 최대 줄 수 (오래된 줄부터 화면에서만 빠짐) + public int MaxLines { get; set; } = 20000; + + public int Count => _lines.Count - (_filler != null ? 1 : 0); + + public LogPanel() + { + var root = XamlLoader.LoadElement("LogPanel.xaml"); + Content = root; + + _list = root.Find("lstLog"); + _info = root.Find("txtInfo"); + _list.ItemsSource = _lines; + + root.Find