1288 lines
55 KiB
C#
1288 lines
55 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
|
|
|
|
namespace ControlServer
|
|
{
|
|
public partial class Form1 : Form
|
|
{
|
|
private List<TerminalInfo> terminalList = new List<TerminalInfo>();
|
|
|
|
// 동시성 제어를 위해 해시맵(Dictionary) 접근 시 락(Lock) 객체 분리 또는 보완
|
|
private readonly Dictionary<string, TcpClient> _connectedAgents = new Dictionary<string, TcpClient>();
|
|
private readonly Dictionary<string, AgentMonitorForm> _openedMonitorForms = new Dictionary<string, AgentMonitorForm>();
|
|
private readonly HashSet<string> _completedAgents = new HashSet<string>();
|
|
|
|
private TcpListener _listener;
|
|
private bool _isRunning = false;
|
|
private System.Windows.Forms.Timer _statusTimer;
|
|
private bool _isUpdatePending = false;
|
|
private System.Windows.Forms.Timer _autoControlTimer; // 에이전트 PC 자동 ON/OFF 시간
|
|
|
|
private CancellationTokenSource _updateCts = null; // 60초 타이머 제어를 위한 토큰 소스 및 상태 플래그
|
|
private bool _isTimerRunning = false;
|
|
|
|
private readonly object _syncLock = new object();
|
|
private readonly object _agentLock = new object();
|
|
private readonly DbFileManager _dbFileManager = new DbFileManager();
|
|
|
|
private Size _originalFormSize; // 폼 로드 시 초기 비율 저장
|
|
|
|
public Form1()
|
|
{
|
|
InitializeComponent();
|
|
this.Load += Form1_Load;
|
|
this.FormClosing += Form1_FormClosing; // 서버 종료 시 자원 해제
|
|
this.WindowState = FormWindowState.Maximized; // 폼 최대화 설정
|
|
this.Resize += new EventHandler(Form1_Resize); // Resize 이벤트 연결
|
|
|
|
InitializeButtonDesign();
|
|
// 둥근 패널 설정
|
|
pannGray.BackColor = Color.White;
|
|
pannBlue.BackColor = Color.White;
|
|
pannGray.BackgroundImage = null;
|
|
pannBlue.BackgroundImage = null;
|
|
pannGray.CornerRadius = 30; // 숫자가 클수록 더 둥글어짐
|
|
pannBlue.CornerRadius = 30;
|
|
|
|
// 텍스트 박스 스타일 및 정렬 설정
|
|
txtSearch.BorderStyle = BorderStyle.None; // 보더 선 제거
|
|
txtSearch.BackColor = Color.LightGray; // 배경색 회색 설정 (원하는 밝기로 조절 가능)
|
|
txtSearch.TextAlign = HorizontalAlignment.Left; // 가로 왼쪽 정렬
|
|
txtSearch.PlaceholderText = "검색(단말기명, IP)"; // 히든 글씨 설정
|
|
|
|
// 자동 제어 타이머 초기화 (1초 주기)
|
|
_autoControlTimer = new System.Windows.Forms.Timer();
|
|
_autoControlTimer.Interval = 1000;
|
|
_autoControlTimer.Tick += AutoControlTimer_Tick;
|
|
_autoControlTimer.Start();
|
|
|
|
InitDataGridView();
|
|
grdTotList.CellFormatting += grdTotList_CellFormatting;
|
|
txtSearch.SizeChanged += TextBox_SizeChanged;
|
|
btnDataUpdate.Click += btnDataUpdate_Click;
|
|
grdTotList.CellDoubleClick += grdTotList_CellDoubleClick;
|
|
grdTotList.CellMouseClick += grdTotList_CellMouseClick; // [누락 수정] 이벤트 바인딩 추가
|
|
}
|
|
|
|
private void InitDataGridView()
|
|
{
|
|
// 기본 설정
|
|
grdTotList.RowHeadersVisible = false; // 왼쪽 화살표(행 헤더) 숨김
|
|
grdTotList.AllowUserToAddRows = false; // 빈 빈칸 추가 방지
|
|
grdTotList.AllowUserToResizeRows = false; // 행 높이 수동 조절 방지
|
|
grdTotList.AllowUserToResizeColumns = false; // 열 너비 수동 조절 방지
|
|
grdTotList.ReadOnly = true; // 읽기 전용
|
|
grdTotList.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // 행 단위 선택
|
|
grdTotList.MultiSelect = false; // 다중 선택 방지
|
|
|
|
grdTotList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; // 열 넓이도 그리드 넓이에 맞게 자동으로 늘어나게 설정
|
|
grdTotList.Columns[3].FillWeight = 60; // 통신설정
|
|
grdTotList.Columns[4].FillWeight = 60; // 프로그램
|
|
grdTotList.Columns[5].FillWeight = 140;
|
|
grdTotList.EnableHeadersVisualStyles = false; // 커스텀 디자인을 적용하기 위해 반드시 false로 설정
|
|
|
|
// 전체 배경 및 테두리 설정
|
|
grdTotList.BackgroundColor = Color.White; // 그리드 바깥쪽 빈 공간 배경색
|
|
grdTotList.BorderStyle = BorderStyle.None; // 그리드 전체 테두리 제거
|
|
|
|
// 셀 테두리 설정 (가로선만 옅은 회색으로)
|
|
grdTotList.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
|
|
grdTotList.GridColor = Color.FromArgb(230, 230, 230);
|
|
|
|
// 헤더(제목 표시줄) 스타일 (진한 남색 배경, 흰색 글씨)
|
|
DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
|
|
Color headerBgColor = Color.FromArgb(44, 62, 80); // #2C3E50 (진한 남색)
|
|
Color headerFgColor = Color.White;
|
|
|
|
headerStyle.BackColor = headerBgColor;
|
|
headerStyle.ForeColor = headerFgColor;
|
|
headerStyle.Font = new Font("맑은 고딕", 10F, FontStyle.Bold);
|
|
headerStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
|
|
// ★ 행 클릭 시 헤더가 반전되거나 포커스 색상으로 변하는 것을 방지
|
|
headerStyle.SelectionBackColor = headerBgColor;
|
|
headerStyle.SelectionForeColor = headerFgColor;
|
|
|
|
grdTotList.ColumnHeadersDefaultCellStyle = headerStyle;
|
|
grdTotList.ColumnHeadersHeight = 40;
|
|
grdTotList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
|
|
|
|
// 행 선택 시 헤더 스타일 상속으로 인한 테두리 및 색상 왜곡 방지
|
|
grdTotList.EnableHeadersVisualStyles = false;
|
|
|
|
// 기본 셀(데이터) 스타일
|
|
DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();
|
|
cellStyle.BackColor = Color.White;
|
|
cellStyle.ForeColor = Color.FromArgb(50, 50, 50); // 완전 검정보다 부드러운 짙은 회색
|
|
cellStyle.Font = new Font("맑은 고딕", 9.5F, FontStyle.Regular);
|
|
cellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
// 선택했을 때 형광 파란색 대신 옅은 회색으로 표시
|
|
cellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249);
|
|
cellStyle.SelectionForeColor = Color.Black;
|
|
grdTotList.DefaultCellStyle = cellStyle;
|
|
|
|
// 행 높이 넉넉하게 설정
|
|
grdTotList.RowTemplate.Height = 45;
|
|
|
|
grdTotList.ClearSelection();
|
|
}
|
|
|
|
private void LoadSavedTerminals()
|
|
{
|
|
lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
|
|
}
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
_originalFormSize = this.Size;
|
|
LoadSavedTerminals();
|
|
InitStatusTimer();
|
|
|
|
// 1. 단말기 등록 여부와 상관없이 "서버 포트 오픈"은 무조건 실행
|
|
System.Diagnostics.Debug.WriteLine("[서버 로드] StartServerAsync 호출 시도");
|
|
|
|
Task.Run(async () => await StartServerAsync()).ContinueWith(t =>
|
|
{
|
|
if (t.IsFaulted)
|
|
{
|
|
string errMsg = t.Exception?.GetBaseException().Message;
|
|
System.Diagnostics.Debug.WriteLine($"서버 구동 태스크 오류: {errMsg}");
|
|
}
|
|
});
|
|
|
|
// 2. 단말기 상태 체크 부분
|
|
if (grdTotList.Rows.Count > 0)
|
|
{
|
|
Task.Run(() => CheckAllAliveStatus());
|
|
}
|
|
else
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("등록된 단말기가 없어 서버를 대기 상태로 유지합니다. 단말기를 등록해주세요.");
|
|
}
|
|
}
|
|
|
|
// 폼 크기 변동 시 호출되는 이벤트
|
|
private void Form1_Resize(object sender, EventArgs e)
|
|
{
|
|
if (this.WindowState == FormWindowState.Minimized) return;
|
|
|
|
int margin = (int)(this.Width * 0.05);
|
|
int contentWidth = this.Width - (margin * 2);
|
|
|
|
// 1. 그리드뷰 위치 및 크기 조정
|
|
grdTotList.Width = contentWidth;
|
|
grdTotList.Left = margin;
|
|
grdTotList.Height = (int)(this.Height * 0.6);
|
|
|
|
pannGray.Width = (int)(grdTotList.Width * 0.2);
|
|
pannBlue.Width = (int)(grdTotList.Width * 0.2);
|
|
pannGray.Left = (int)(this.Width / 2 - pannGray.Width - 30);
|
|
pannBlue.Left = (int)(this.Width / 2 + 30);
|
|
lblGrayTitle.Width = (int)(grdTotList.Width * 0.2);
|
|
lblBlueTitle.Width = (int)(grdTotList.Width * 0.2);
|
|
lblTotCnt.Width = (int)(grdTotList.Width * 0.2);
|
|
lblRunningCnt.Width = (int)(grdTotList.Width * 0.2);
|
|
|
|
// 2. 상단/하단 컨트롤들도 동일하게 좌우 여백 적용
|
|
txtSearch.Left = margin;
|
|
txtSearch.Width = (int)(contentWidth * 0.2);
|
|
imgSearch.Left = (int)(txtSearch.Left + txtSearch.Width + 10);
|
|
btnRegister.Left = (int)(grdTotList.Width + grdTotList.Left - btnRegister.Width);
|
|
btnDataUpdate.Left = (int)(btnRegister.Left - btnDataUpdate.Width - 10);
|
|
|
|
// 강제 중앙 정렬 함수를 호출
|
|
CenterControl(grdTotList);
|
|
}
|
|
|
|
// 특정 컨트롤을 폼의 가로 중앙에 배치하는 헬퍼 메서드
|
|
private void CenterControl(Control ctrl)
|
|
{
|
|
ctrl.Left = (this.Width - ctrl.Width) / 2;
|
|
}
|
|
|
|
// 버튼 디자인 초기화
|
|
private void InitializeButtonDesign()
|
|
{
|
|
// 버튼의 기본 테두리 제거를 위해 FlatStyle을 Flat으로 설정
|
|
btnDataUpdate.FlatStyle = FlatStyle.Flat;
|
|
btnDataUpdate.FlatAppearance.BorderSize = 0; // 보더 선 제거
|
|
btnDataUpdate.BackColor = Color.White; // 원하시는 배경색
|
|
|
|
btnRegister.FlatStyle = FlatStyle.Flat;
|
|
btnRegister.FlatAppearance.BorderSize = 0; // 보더 선 제거
|
|
btnRegister.BackColor = Color.RoyalBlue;
|
|
|
|
// 둥글게 그리기 이벤트 연결
|
|
btnDataUpdate.Paint += Button_Paint;
|
|
btnRegister.Paint += Button_Paint;
|
|
}
|
|
|
|
// 둥근 모서리 그리기 메서드
|
|
private void Button_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
System.Windows.Forms.Button btn = (System.Windows.Forms.Button)sender;
|
|
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
|
|
|
// 둥근 사각형 그리기
|
|
int radius = 20; // 둥근 정도
|
|
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
|
|
path.AddArc(0, 0, radius, radius, 180, 90);
|
|
path.AddArc(btn.Width - radius, 0, radius, radius, 270, 90);
|
|
path.AddArc(btn.Width - radius, btn.Height - radius, radius, radius, 0, 90);
|
|
path.AddArc(0, btn.Height - radius, radius, radius, 90, 90);
|
|
path.CloseFigure();
|
|
|
|
btn.Region = new Region(path);
|
|
}
|
|
|
|
private void InitStatusTimer()
|
|
{
|
|
_statusTimer = new System.Windows.Forms.Timer();
|
|
_statusTimer.Interval = 5000;
|
|
_statusTimer.Tick += (s, e) =>
|
|
{
|
|
// 타이머 틱은 UI 스레드에서 돌므로 비동기로 상태 체크 유도
|
|
Task.Run(() => CheckAllAliveStatus());
|
|
};
|
|
_statusTimer.Start();
|
|
}
|
|
|
|
// [구조 개선] UI 전체를 Invoke로 묶어 돌리던 무거운 방식을 내부 값 갱신 시에만 분리하도록 최적화
|
|
private void CheckAllAliveStatus()
|
|
{
|
|
try
|
|
{
|
|
List<(int RowIndex, string Ip, string Mac)> targets = new List<(int, string, string)>();
|
|
|
|
this.Invoke(new Action(() =>
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
string ip = row.Cells[2].Value?.ToString()?.Trim();
|
|
string mac = row.Cells[1].Value?.ToString()?.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim() ?? string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(ip))
|
|
{
|
|
targets.Add((row.Index, ip, mac));
|
|
}
|
|
}
|
|
}));
|
|
|
|
foreach (var target in targets)
|
|
{
|
|
bool isAlive = false;
|
|
lock (_agentLock)
|
|
{
|
|
// 복잡한 Poll 검사를 제거하고, 딕셔너리에 존재하며 Connected 상태인지 확인
|
|
if (_connectedAgents.TryGetValue(target.Ip, out TcpClient client))
|
|
{
|
|
if (client != null && client.Connected)
|
|
{
|
|
isAlive = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// UI 업데이트
|
|
this.BeginInvoke(new Action(() =>
|
|
{
|
|
if (target.RowIndex >= grdTotList.Rows.Count) return;
|
|
var row = grdTotList.Rows[target.RowIndex];
|
|
|
|
if (isAlive)
|
|
{
|
|
row.Cells[4].Value = "🟢 Start";
|
|
row.Cells[4].Style.BackColor = Color.Green;
|
|
}
|
|
else
|
|
{
|
|
row.Cells[4].Value = "🟢 Stop";
|
|
row.Cells[4].Style.BackColor = Color.Red;
|
|
}
|
|
row.Cells[4].Style.ForeColor = Color.Black;
|
|
row.Cells[4].Style.BackColor = Color.White;
|
|
row.Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
}));
|
|
}
|
|
|
|
this.BeginInvoke(new Action(() => UpdateRunningCount()));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"상태 체크 도중 예외 발생: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task StartServerAsync()
|
|
{
|
|
if (_isRunning) return;
|
|
|
|
try
|
|
{
|
|
_listener = new TcpListener(IPAddress.Any, 9999);
|
|
_listener.Start();
|
|
_isRunning = true;
|
|
System.Diagnostics.Debug.WriteLine("서버 구동 성공 (Port: 9999)... 에이전트 접속을 대기합니다.");
|
|
|
|
while (_isRunning)
|
|
{
|
|
// Accept 직전 대기 상태 확인
|
|
System.Diagnostics.Debug.WriteLine("[대기 중] 새로운 에이전트의 접속 소켓 신호를 기다리는 중...");
|
|
|
|
// 클라이언트 접속 대기 (비동기)
|
|
TcpClient client = await _listener.AcceptTcpClientAsync();
|
|
|
|
// 소켓 연결 자체가 성공했을 때
|
|
System.Diagnostics.Debug.WriteLine("[소켓 연결 성공] 누군가 9999 포트로 연결했습니다! 핸들러로 넘깁니다.");
|
|
|
|
_ = Task.Run(() => HandleAgentAsync(client));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"서버 루프 치명적 오류: {ex.Message}");
|
|
this.BeginInvoke(new Action(() => MessageBox.Show("서버 구동 오류: " + ex.Message)));
|
|
}
|
|
finally
|
|
{
|
|
_isRunning = false;
|
|
try { _listener?.Stop(); } catch { }
|
|
System.Diagnostics.Debug.WriteLine("서버 구동이 완전히 중지되었습니다. (_isRunning = false)");
|
|
}
|
|
}
|
|
|
|
// 동기 IO 함수들(StreamReader.ReadLine)을 비동기(ReadLineAsync)로 변경하여 리소스 낭비 방지
|
|
private async Task HandleAgentAsync(TcpClient client)
|
|
{
|
|
string clientIP = string.Empty;
|
|
try
|
|
{
|
|
clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
|
|
System.Diagnostics.Debug.WriteLine($"[핸들러 진입] IP: {clientIP} 스트림 분석 시작");
|
|
|
|
using (NetworkStream stream = client.GetStream())
|
|
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
|
|
{
|
|
string receivedMac = await reader.ReadLineAsync();
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 MAC 주소: '{receivedMac}'");
|
|
|
|
if (!string.IsNullOrEmpty(receivedMac))
|
|
{
|
|
// 등록된 단말기인지 UI 스레드에서 먼저 판별 및 확인
|
|
bool isRegistered = false;
|
|
this.Invoke(new Action(() =>
|
|
{
|
|
isRegistered = UpdateAgentStatusByMac(receivedMac, clientIP);
|
|
}));
|
|
|
|
// 등록되지 않은 단말기라면 소켓 끊고 핸들러 종료!
|
|
if (!isRegistered)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 미등록 단말기이므로 소켓을 강제 종료합니다.");
|
|
try { client.Close(); } catch { }
|
|
return;
|
|
}
|
|
|
|
// ---------------- 등록된 단말기만 아래 로직을 수행 ----------------
|
|
lock (_agentLock)
|
|
{
|
|
if (_connectedAgents.ContainsKey(clientIP))
|
|
{
|
|
try { _connectedAgents[clientIP].Close(); } catch { }
|
|
_connectedAgents.Remove(clientIP);
|
|
}
|
|
_connectedAgents[clientIP] = client;
|
|
}
|
|
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 정상 등록 단말기 딕셔너리 추가 완료.");
|
|
|
|
string terminalName = await Task.Run(() => GetTerminalNameByIp(clientIP));
|
|
if (string.IsNullOrEmpty(terminalName)) terminalName = $"Terminal_{clientIP.Replace(".", "_")}";
|
|
|
|
string initialDbData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
|
|
await SendDataToSingleAgentAsync(client, initialDbData); // 에이전트로 데이터 전송
|
|
|
|
while (client.Connected)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 메시지 받기 전");
|
|
string agentMsg = await reader.ReadLineAsync();
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 메시지: {(agentMsg ?? "NULL/연결종료")}");
|
|
if (agentMsg == null)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 읽기 종료(null). 루프 탈출.");
|
|
break;
|
|
}
|
|
|
|
// (예: MAC|메시지)
|
|
if (agentMsg.Contains("display_complete"))
|
|
{
|
|
// 기다리지 않고, 즉시 해당 에이전트에게 데이터 재전송
|
|
System.Diagnostics.Debug.WriteLine($"[{clientIP}] 완료 신호 수신. 즉시 데이터 갱신을 진행합니다.");
|
|
string newData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
|
|
await SendDataToSingleAgentAsync(client, newData); // 현재 접속된 소켓(client)을 통해 즉시 데이터 전송
|
|
|
|
// 모니터링 폼이 열려있다면 즉시 데이터 전달
|
|
lock (_syncLock)
|
|
{
|
|
if (_openedMonitorForms.TryGetValue(clientIP, out var monitorForm) && !monitorForm.IsDisposed)
|
|
{
|
|
monitorForm.BeginInvoke(new Action(() => monitorForm.UpdateMonitorData(newData)));
|
|
}
|
|
}
|
|
}
|
|
// 에이전트가 보내는 프로토콜 정의
|
|
else if (agentMsg.Contains("data_update"))
|
|
{
|
|
// 그리드의 마지막 통신 시간을 현재 시간으로 업데이트
|
|
this.BeginInvoke(new Action(() =>
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
|
|
string gridIp = row.Cells[2].Value?.ToString()?.Trim();
|
|
|
|
// IP로 매칭
|
|
bool isMatch = (!string.IsNullOrEmpty(clientIP) && gridIp == clientIP);
|
|
|
|
if (isMatch)
|
|
{
|
|
row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
break;
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{clientIP}] 핸들러 내부 루프 예외: " + ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
lock (_agentLock)
|
|
{
|
|
if (!string.IsNullOrEmpty(clientIP) && _connectedAgents.ContainsKey(clientIP))
|
|
{
|
|
_connectedAgents.Remove(clientIP);
|
|
}
|
|
}
|
|
try { client.Close(); } catch { }
|
|
Task.Run(() => CheckAllAliveStatus());
|
|
}
|
|
}
|
|
|
|
private void ProcessAgentCompletion(string agentIp)
|
|
{
|
|
lock (_syncLock)
|
|
{
|
|
// 1. 완료 목록에 추가
|
|
if (!_completedAgents.Contains(agentIp))
|
|
{
|
|
_completedAgents.Add(agentIp);
|
|
Console.WriteLine($"[완료 신호 수신] 단말기 IP: {agentIp} ({_completedAgents.Count}대 완료)");
|
|
}
|
|
|
|
// 현재 연결 상태인 에이전트들의 총 개수 파악
|
|
int totalExpected = 0;
|
|
lock (_agentLock) { totalExpected = _connectedAgents.Count; }
|
|
|
|
if (totalExpected == 0) return;
|
|
|
|
// 2. 첫 번째 완료 신호가 들어왔을 때 60초 타이머 기동
|
|
if (!_isTimerRunning)
|
|
{
|
|
_isTimerRunning = true;
|
|
_updateCts = new CancellationTokenSource();
|
|
|
|
var token = _updateCts.Token;
|
|
Task.Run(async () =>
|
|
{
|
|
Console.WriteLine("첫 번째 완료 신호 감지. 60초 대기 타이머를 시작합니다...");
|
|
try
|
|
{
|
|
// 60초 동안 대기 (모든 단말 완료 시 외부에서 Cancel() 요청하여 조기 종료됨)
|
|
await Task.Delay(60000, token);
|
|
|
|
// 타임아웃 발생: 60초 내에 다 못 받은 상황
|
|
Console.WriteLine("[타임아웃] 60초 제한 시간이 지났습니다. 낙오 단말기 처리를 시작합니다.");
|
|
|
|
List<string> stragglers = new List<string>();
|
|
lock (_syncLock)
|
|
{
|
|
lock (_agentLock)
|
|
{
|
|
foreach (var ip in _connectedAgents.Keys)
|
|
{
|
|
if (!_completedAgents.Contains(ip))
|
|
{
|
|
stragglers.Add(ip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 낙오 단말기 연결 끊기 및 상태 변경
|
|
foreach (var ip in stragglers)
|
|
{
|
|
Console.WriteLine($"[낙오 단말 끊기] 60초 내 미완료 단말 제거: {ip}");
|
|
lock (_agentLock)
|
|
{
|
|
if (_connectedAgents.TryGetValue(ip, out TcpClient client))
|
|
{
|
|
try { client.Close(); } catch { }
|
|
_connectedAgents.Remove(ip);
|
|
}
|
|
}
|
|
|
|
// UI 스레드에서 해당 IP의 그리드 상태를 Stop(Red)으로 업데이트
|
|
this.BeginInvoke(new Action(() =>
|
|
{
|
|
UpdateTerminalGridStatusToStop(ip);
|
|
}));
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
// 60초 이전에 모든 단말기가 정상 완료되어 취소된 경우
|
|
Console.WriteLine("모든 단말기가 60초 내에 연출을 완료하여 대기를 조기 종료합니다.");
|
|
}
|
|
finally
|
|
{
|
|
// 3. 타이머가 끝났거나 조기 종료되었으므로 공통 다음 파일 전송 및 초기화
|
|
Console.WriteLine("다음 데이터 파일 갱신 처리를 시작합니다.");
|
|
SendDataToAllAgents();
|
|
|
|
lock (_syncLock)
|
|
{
|
|
_completedAgents.Clear();
|
|
_isTimerRunning = false;
|
|
_updateCts?.Dispose();
|
|
_updateCts = null;
|
|
}
|
|
|
|
// 상태 갱신
|
|
Task.Run(() => CheckAllAliveStatus());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 낙오된 단말기를 UI 상에서 강제로 Stop 처리하기 위한 헬퍼 메서드
|
|
private void UpdateTerminalGridStatusToStop(string ip)
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
if (row.Cells[2].Value?.ToString()?.Trim() == ip)
|
|
{
|
|
row.Cells[4].Value = "🔴 Stop";
|
|
row.Cells[4].Style.BackColor = Color.White;
|
|
row.Cells[4].Style.ForeColor = Color.Black;
|
|
break;
|
|
}
|
|
}
|
|
UpdateRunningCount();
|
|
}
|
|
|
|
private bool UpdateAgentStatusByMac(string clientMac, string clientIP)
|
|
{
|
|
// 에이전트가 보내온 MAC 주소 정제
|
|
string cleanClientMac = string.IsNullOrEmpty(clientMac) ?
|
|
string.Empty : clientMac.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim();
|
|
|
|
bool isMapped = false;
|
|
|
|
// grdTotList 순회하며 등록된 단말기인지 확인
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
|
|
string gridIp = row.Cells[2].Value?.ToString()?.Trim() ?? string.Empty;
|
|
string gridMac = row.Cells[1].Value?.ToString()?.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim() ?? string.Empty;
|
|
|
|
// 1순위: IP 주소 매칭 또는 2순위: MAC 주소 일치
|
|
if (gridIp == clientIP || (!string.IsNullOrEmpty(gridMac) && gridMac == cleanClientMac))
|
|
{
|
|
if (gridIp != clientIP)
|
|
{
|
|
row.Cells[2].Value = clientIP; // 최신 IP로 갱신
|
|
}
|
|
|
|
if (gridMac != cleanClientMac && !string.IsNullOrEmpty(cleanClientMac))
|
|
{
|
|
row.Cells[1].Value = cleanClientMac; // MAC 주소 보정
|
|
Console.WriteLine($"[그리드 보정] IP: {clientIP} 단말기의 MAC 주소를 {cleanClientMac}으로 갱신했습니다.");
|
|
}
|
|
|
|
// 상태를 Start로 변경
|
|
row.Cells[4].Value = "🟢 Start";
|
|
row.Cells[4].Style.BackColor = Color.White;
|
|
row.Cells[4].Style.ForeColor = Color.Black;
|
|
row.Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
|
|
row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
isMapped = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ★ 미등록 에이전트 자동 추가 로직 제거 및 로그 출력
|
|
if (!isMapped)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[연결 거부] 등록되지 않은 단말기 접속 시도 - IP: {clientIP}, MAC: {cleanClientMac}");
|
|
}
|
|
|
|
// 켜져 있는 대수 카운트 갱신
|
|
UpdateRunningCount();
|
|
|
|
return isMapped; // 매핑 성공 여부 반환
|
|
}
|
|
|
|
private void UpdateRunningCount()
|
|
{
|
|
int startCount = 0;
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
if (row.Cells[4].Value?.ToString().Contains("Start") == true)
|
|
{
|
|
startCount++;
|
|
}
|
|
}
|
|
lblRunningCnt.Text = startCount.ToString() + "대";
|
|
}
|
|
|
|
private void btnRegister_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
using (Form2 form = new Form2())
|
|
{
|
|
if (form.ShowDialog() == DialogResult.OK)
|
|
{
|
|
TerminalInfo data = form.TerminalData;
|
|
|
|
_dbFileManager.CreateAndSaveTerminalFile(data.TerminalName); // 등록 시점에 해당 단말기 이름으로 파일 생성
|
|
|
|
grdTotList.Rows.Add(
|
|
data.TerminalName,
|
|
data.Mac,
|
|
data.IP,
|
|
"설정",
|
|
"Stop",
|
|
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
data.Port,
|
|
"5", // 에이전트 화면 페이지 표출 시간 디폴트값
|
|
"", // ON 시간 (초기값 없음)
|
|
"" // OFF 시간 (초기값 없음)
|
|
);
|
|
|
|
int rowIndex = grdTotList.Rows.Count - 1;
|
|
|
|
//grdTotList.Rows[rowIndex].Cells[4].Style.BackColor = Color.Red;
|
|
//grdTotList.Rows[rowIndex].Cells[4].Style.ForeColor = Color.White;
|
|
//grdTotList.Rows[rowIndex].Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
|
|
//grdTotList.Rows[rowIndex].Cells[3].Style.BackColor = Color.LightGray;
|
|
//grdTotList.Rows[rowIndex].Cells[3].Style.ForeColor = Color.Black;
|
|
//grdTotList.Rows[rowIndex].Cells[3].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
|
|
|
lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
|
|
|
|
if (!_isRunning)
|
|
{
|
|
_ = Task.Run(() => StartServerAsync());
|
|
}
|
|
|
|
Task.Run(() => CheckAllAliveStatus());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void btnDataUpdate_Click(object sender, EventArgs e)
|
|
{
|
|
// 데이터 갱신 작업 실행
|
|
Task.Run(() =>
|
|
{
|
|
SendDataToAllAgents();
|
|
|
|
this.BeginInvoke(new Action(() =>
|
|
{
|
|
CheckAllAliveStatus();
|
|
}));
|
|
});
|
|
}
|
|
|
|
private async void grdTotList_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
if (e.ColumnIndex < 3)
|
|
{
|
|
if (e.RowIndex < 0) return;
|
|
|
|
string terminalName = grdTotList.Rows[e.RowIndex].Cells[0].Value?.ToString();
|
|
string targetIP = grdTotList.Rows[e.RowIndex].Cells[2].Value?.ToString();
|
|
|
|
// 실제 에이전트 연결 상태 확인
|
|
bool isConnected = false;
|
|
lock (_agentLock)
|
|
{
|
|
if (!string.IsNullOrEmpty(targetIP) && _connectedAgents.TryGetValue(targetIP, out TcpClient client))
|
|
{
|
|
isConnected = (client != null && client.Connected);
|
|
}
|
|
}
|
|
|
|
if (!isConnected)
|
|
{
|
|
MessageBox.Show("현재 에이전트와 연결되어 있지 않습니다.\n연결 후 다시 시도해 주세요.", "연결 확인", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return; // 연결되지 않았으면 여기서 중단
|
|
}
|
|
|
|
// 1. 이미 열려있으면 업데이트만 수행
|
|
if (_openedMonitorForms.TryGetValue(targetIP, out AgentMonitorForm existingForm) && !existingForm.IsDisposed)
|
|
{
|
|
string updatedXml = await Task.Run(() => _dbFileManager.CreateAndSaveTerminalFile(terminalName));
|
|
existingForm.UpdateMonitorData(updatedXml);
|
|
existingForm.Activate();
|
|
return;
|
|
}
|
|
|
|
// 2. 폼 생성
|
|
AgentMonitorForm monitorForm = new AgentMonitorForm();
|
|
monitorForm.Text = $"[가상 모니터링] {terminalName} ({targetIP})";
|
|
|
|
// 폼이 로드될 때 소켓 통신을 하지 않도록 설정하는 플래그를 추가하는 것이 좋습니다.
|
|
// monitorForm.DisableSocketCommunication = true;
|
|
|
|
monitorForm.Show();
|
|
|
|
// 3. 데이터 로드 및 적용
|
|
string currentXml = await Task.Run(() => _dbFileManager.CreateAndSaveTerminalFile(terminalName));
|
|
monitorForm.UpdateMonitorData(currentXml);
|
|
|
|
lock (_syncLock) { _openedMonitorForms[targetIP] = monitorForm; }
|
|
}
|
|
else { }
|
|
|
|
}
|
|
|
|
private void grdTotList_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
|
|
{
|
|
grdTotList.ClearSelection();
|
|
if (e.RowIndex < 0) return;
|
|
|
|
var row = grdTotList.Rows[e.RowIndex];
|
|
string terminalName = row.Cells[0].Value?.ToString() ?? "";
|
|
string terminalIp = row.Cells[2].Value?.ToString() ?? "";
|
|
|
|
if (e.ColumnIndex == 3) // 설정 버튼 클릭
|
|
{
|
|
string filePath = _dbFileManager.GetTerminalFilePath(terminalName); //파일 확보
|
|
|
|
using (Form3 form = new Form3(this, terminalName, terminalIp, filePath))
|
|
{
|
|
form.ShowDialog();
|
|
}
|
|
}
|
|
else if (e.ColumnIndex == 4) // Start/Stop 상태 제어 버튼 클릭
|
|
{
|
|
var grdRow = grdTotList.Rows[e.RowIndex];
|
|
string ip = grdRow.Cells[2].Value?.ToString();
|
|
string mac = grdRow.Cells[1].Value?.ToString();
|
|
string status = grdRow.Cells[4].Value?.ToString() ?? "Stop";
|
|
|
|
ExecuteAgentControl(ip, mac, status);
|
|
}
|
|
}
|
|
|
|
// 그리드 셀 클릭 로직을 공통 함수로 분리
|
|
private void ExecuteAgentControl(string ip, string mac, string currentStatus)
|
|
{
|
|
// 현재 상태에 따라 적절한 동작 수행
|
|
if (currentStatus.Contains("Start"))
|
|
{
|
|
// 끄기 (Stop)
|
|
SendStopCommandToAgent(ip);
|
|
UpdateGridStatus(ip, "Stop", Color.Red);
|
|
}
|
|
else
|
|
{
|
|
// 켜기 (Start)
|
|
if (!string.IsNullOrEmpty(mac))
|
|
{
|
|
SendWakeOnLan(mac);
|
|
UpdateGridStatus(ip, "Start", Color.Green);
|
|
}
|
|
}
|
|
UpdateRunningCount();
|
|
}
|
|
|
|
// 그리드 상태 업데이트 헬퍼
|
|
private void UpdateGridStatus(string ip, string status, Color color)
|
|
{
|
|
this.Invoke(new Action(() =>
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.Cells[2].Value?.ToString() == ip)
|
|
{
|
|
row.Cells[4].Value = status;
|
|
row.Cells[4].Style.ForeColor = Color.Black;
|
|
row.Cells[4].Style.BackColor = Color.White;
|
|
row.Cells[4].Style.ForeColor = color;
|
|
|
|
if (status == "Start")
|
|
{
|
|
row.Cells[4].Value = "🟢 Start"; // 특수기호 사용
|
|
}
|
|
else
|
|
{
|
|
row.Cells[4].Value = "🔴 Stop";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
// 프로글매 자동 on/off
|
|
private void AutoControlTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
// 현재 시간(초단위까지)
|
|
DateTime now = DateTime.Now;
|
|
string currentTimeStr = now.ToString("HH:mm:ss");
|
|
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue;
|
|
|
|
string ip = row.Cells[2].Value?.ToString();
|
|
string mac = row.Cells[1].Value?.ToString();
|
|
string onTimeStr = row.Cells[8].Value?.ToString(); // 예: "09:00:00"
|
|
string offTimeStr = row.Cells[9].Value?.ToString(); // 예: "18:00:00"
|
|
string currentStatus = row.Cells[4].Value?.ToString();
|
|
|
|
if (string.IsNullOrEmpty(ip)) continue;
|
|
|
|
if (currentTimeStr == onTimeStr && currentStatus.Contains("Stop"))
|
|
{
|
|
// 켜기 명령 실행
|
|
SendWakeOnLan(mac);
|
|
UpdateGridStatus(ip, "Start", Color.Green);
|
|
}
|
|
else if (currentTimeStr == offTimeStr && currentStatus.Contains("Start"))
|
|
{
|
|
// 끄기 명령 실행
|
|
SendStopCommandToAgent(ip);
|
|
UpdateGridStatus(ip, "Stop", Color.Red);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SendStopCommandToAgent(string ip)
|
|
{
|
|
try
|
|
{
|
|
lock (_agentLock)
|
|
{
|
|
if (_connectedAgents.TryGetValue(ip, out TcpClient client) && client.Connected)
|
|
{
|
|
byte[] msg = Encoding.UTF8.GetBytes("STOP\n");
|
|
client.GetStream().Write(msg, 0, msg.Length);
|
|
client.GetStream().Flush();
|
|
System.Diagnostics.Debug.WriteLine($"[서버] {ip}에게 STOP 명령 전송 완료!");
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("해당 에이전트는 현재 서버에 소켓으로 연결된 상태가 아닙니다.");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("종료 명령 전송 실패: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private void SendWakeOnLan(string macAddress)
|
|
{
|
|
try
|
|
{
|
|
string cleanMac = macAddress.Replace("-", "").Replace(":", "").Replace(" ", "").Trim();
|
|
if (cleanMac.Length != 12) return;
|
|
|
|
byte[] bytes = new byte[102];
|
|
for (int i = 0; i < 6; i++) bytes[i] = 0xFF;
|
|
|
|
byte[] macBytes = new byte[6];
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
macBytes[i] = Convert.ToByte(cleanMac.Substring(i * 2, 2), 16);
|
|
}
|
|
|
|
for (int i = 1; i <= 16; i++)
|
|
{
|
|
Array.Copy(macBytes, 0, bytes, i * 6, 6);
|
|
}
|
|
|
|
using (UdpClient client = new UdpClient())
|
|
{
|
|
client.Connect(IPAddress.Parse("255.255.255.255"), 9);
|
|
client.Send(bytes, bytes.Length);
|
|
}
|
|
Console.WriteLine($"[WOL 전송 완료] MAC: {cleanMac}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("WOL 패킷 송신 예외 오류: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private void SendDataToAllAgents()
|
|
{
|
|
lock (_agentLock)
|
|
{
|
|
foreach (var kvp in _connectedAgents)
|
|
{
|
|
string agentIP = kvp.Key;
|
|
TcpClient client = kvp.Value;
|
|
|
|
if (client != null && client.Connected)
|
|
{
|
|
try
|
|
{
|
|
string terminalName = GetTerminalNameByIp(agentIP);
|
|
string xmlData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
|
|
|
|
//NetworkStream 대신 StreamWriter를 사용하여 명확하게 데이터 전송
|
|
using (NetworkStream stream = client.GetStream())
|
|
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true })
|
|
{
|
|
writer.WriteLine(xmlData); // Line 단위 전송
|
|
}
|
|
|
|
Console.WriteLine($"[{terminalName}] 데이터 전송 성공.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{agentIP}] 데이터 전송 실패: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 단일 전송 비동기화 지원을 통한 네트워크 병목 방지
|
|
private async Task SendDataToSingleAgentAsync(TcpClient client, string message)
|
|
{
|
|
try
|
|
{
|
|
if (client != null && client.Connected)
|
|
{
|
|
byte[] data = Encoding.UTF8.GetBytes(message + "\n");
|
|
NetworkStream stream = client.GetStream();
|
|
await stream.WriteAsync(data, 0, data.Length);
|
|
await stream.FlushAsync();
|
|
|
|
System.Diagnostics.Debug.WriteLine("[데이터 전송 완료] 성공");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"단일 전송 실패: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void UpdateVirtualMonitorFrame(string ip, string xmlData)
|
|
{
|
|
lock (_syncLock)
|
|
{
|
|
if (_openedMonitorForms.TryGetValue(ip, out var form) && form != null && !form.IsDisposed)
|
|
{
|
|
// 폼이 호출 가능(Load 완료)한 상태인지 확인
|
|
if (form.InvokeRequired)
|
|
{
|
|
form.BeginInvoke(new Action(() =>
|
|
{
|
|
try
|
|
{
|
|
form.UpdateMonitorData(xmlData);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("모니터 업데이트 실패: " + ex.Message);
|
|
}
|
|
}));
|
|
}
|
|
else
|
|
{
|
|
form.UpdateMonitorData(xmlData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[알림] {ip}에 해당하는 모니터링 폼이 아직 활성화되지 않았습니다.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetTerminalNameByIp(string ip)
|
|
{
|
|
string name = string.Empty;
|
|
|
|
if (this.InvokeRequired)
|
|
{
|
|
this.Invoke(new Action(() => name = FindNameInGrid(ip)));
|
|
}
|
|
else
|
|
{
|
|
name = FindNameInGrid(ip);
|
|
}
|
|
|
|
return string.IsNullOrEmpty(name) ? $"Terminal_{ip.Replace(".", "_")}" : name;
|
|
}
|
|
|
|
private string FindNameInGrid(string ip)
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.Cells[2].Value?.ToString() == ip)
|
|
{
|
|
return row.Cells[0].Value?.ToString();
|
|
}
|
|
}
|
|
return string.Empty;
|
|
}
|
|
|
|
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
_isRunning = false;
|
|
_statusTimer?.Stop();
|
|
try { _listener?.Stop(); } catch { }
|
|
|
|
lock (_agentLock)
|
|
{
|
|
foreach (var client in _connectedAgents.Values)
|
|
{
|
|
try { client.Close(); } catch { }
|
|
}
|
|
_connectedAgents.Clear();
|
|
}
|
|
}
|
|
|
|
private void pannGray_Paint(object sender, PaintEventArgs e) { }
|
|
private void pannBlue_Paint(object sender, PaintEventArgs e) { }
|
|
|
|
public void SendDataToSingleAgent(string ip, string message)
|
|
{
|
|
// _connectedAgents 딕셔너리에서 해당 IP를 찾아 전송하는 로직
|
|
lock (_agentLock) // 안전한 접근을 위해 lock 사용
|
|
{
|
|
if (_connectedAgents.ContainsKey(ip))
|
|
{
|
|
TcpClient client = _connectedAgents[ip];
|
|
if (client != null && client.Connected)
|
|
{
|
|
try
|
|
{
|
|
byte[] data = Encoding.UTF8.GetBytes(message + "\n"); // 프로토콜에 맞게 줄바꿈 추가
|
|
NetworkStream stream = client.GetStream();
|
|
stream.Write(data, 0, data.Length);
|
|
stream.Flush();
|
|
|
|
System.Diagnostics.Debug.WriteLine($"[성공] {ip}로 데이터 전송 완료.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[실패] {ip} 데이터 전송 중 오류: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[알림] {ip}에 연결된 에이전트가 없습니다.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 그리드에서 데이터 읽어오기 (표출시간, ON시간, OFF시간)
|
|
public string[] GetProgramTimesByIp(string ip)
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
// IP가 일치하는 행을 찾음
|
|
if (row.Cells[2].Value?.ToString() == ip)
|
|
{
|
|
// 배열 생성 시 한 줄에 쉼표로 구분하여 작성
|
|
return new string[] {
|
|
row.Cells[7].Value?.ToString(),
|
|
row.Cells[8].Value?.ToString(),
|
|
row.Cells[9].Value?.ToString()
|
|
};
|
|
}
|
|
}
|
|
// 기본값 반환
|
|
return new string[] { "5", "", "" };
|
|
}
|
|
|
|
// 그리드에 데이터 저장하기
|
|
public void SaveProgramTimesToGrid(string ip, string display, string on, string off)
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.Cells[2].Value?.ToString() == ip)
|
|
{
|
|
row.Cells[7].Value = display;
|
|
row.Cells[8].Value = on;
|
|
row.Cells[9].Value = off;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void grdTotList_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void imgSearch_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
// 1. 검색어 가져오기 (앞뒤 공백 제거)
|
|
string search = txtSearch.Text.Trim();
|
|
|
|
// 2. 검색어가 빈 값이면 전체 리스트 노출
|
|
if (string.IsNullOrEmpty(search))
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (!row.IsNewRow) row.Visible = true;
|
|
}
|
|
return; // 전체 노출 후 로직 종료
|
|
}
|
|
// 3. 검색어가 입력된 경우 필터링 진행
|
|
else
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue; // 그리드 맨 밑의 빈 행(새 행)은 건너뜀
|
|
|
|
string terminalName = row.Cells[0].Value?.ToString() ?? "";
|
|
string ipAddress = row.Cells[2].Value?.ToString() ?? "";
|
|
|
|
// 단말기명 또는 IP에 검색어가 포함되어 있는지 확인
|
|
if (terminalName.ToLower().Contains(search) || ipAddress.Contains(search))
|
|
{
|
|
row.Visible = true; // 조건에 맞으면 보여줌
|
|
}
|
|
else
|
|
{
|
|
row.Visible = false; // 조건에 안 맞으면 숨김
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private void txtSearch_TextChanged(object sender, EventArgs e)
|
|
{
|
|
// 1. 검색어 가져오기 (앞뒤 공백 제거)
|
|
string search = txtSearch.Text.Trim();
|
|
|
|
// 2. 검색어가 빈 값이면 전체 리스트 노출
|
|
if (string.IsNullOrEmpty(search))
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (!row.IsNewRow) row.Visible = true;
|
|
}
|
|
return; // 전체 노출 후 로직 종료
|
|
}
|
|
// 3. 검색어가 입력된 경우 필터링 진행
|
|
else
|
|
{
|
|
foreach (DataGridViewRow row in grdTotList.Rows)
|
|
{
|
|
if (row.IsNewRow) continue; // 그리드 맨 밑의 빈 행(새 행)은 건너뜀
|
|
|
|
string terminalName = row.Cells[0].Value?.ToString() ?? "";
|
|
string ipAddress = row.Cells[2].Value?.ToString() ?? "";
|
|
|
|
// 단말기명 또는 IP에 검색어가 포함되어 있는지 확인
|
|
if (terminalName.ToLower().Contains(search) || ipAddress.Contains(search))
|
|
{
|
|
row.Visible = true; // 조건에 맞으면 보여줌
|
|
}
|
|
else
|
|
{
|
|
row.Visible = false; // 조건에 안 맞으면 숨김
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Form1_Load_1(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
// 텍스트 박스 크기가 결정되거나 변경될 때 둥근 리전을 생성하여 적용
|
|
private void TextBox_SizeChanged(object sender, EventArgs e)
|
|
{
|
|
System.Windows.Forms.TextBox txt = (System.Windows.Forms.TextBox)sender;
|
|
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
|
|
|
|
// 둥근 모서리의 반지름 설정 (값이 클수록 더 둥글어집니다)
|
|
int radius = 14;
|
|
|
|
if (txt.Width > radius && txt.Height > radius)
|
|
{
|
|
path.AddArc(0, 0, radius, radius, 180, 90);
|
|
path.AddArc(txt.Width - radius, 0, radius, radius, 270, 90);
|
|
path.AddArc(txt.Width - radius, txt.Height - radius, radius, radius, 0, 90);
|
|
path.AddArc(0, txt.Height - radius, radius, radius, 90, 90);
|
|
path.CloseFigure();
|
|
|
|
txt.Region = new Region(path);
|
|
}
|
|
}
|
|
|
|
private void grdTotList_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
|
{
|
|
if (e.ColumnIndex == 4 && e.Value != null)
|
|
{
|
|
string status = e.Value.ToString();
|
|
|
|
if (status.Contains("Stop"))
|
|
{
|
|
e.CellStyle.ForeColor = Color.Red;
|
|
e.CellStyle.SelectionForeColor = Color.Red; // 선택 시에도 빨간 글씨 유지
|
|
|
|
e.CellStyle.BackColor = Color.White;
|
|
e.CellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249); // 표 시안 배경색 유지
|
|
}
|
|
else
|
|
{
|
|
e.CellStyle.ForeColor = Color.Green;
|
|
e.CellStyle.SelectionForeColor = Color.Green; // 선택 시에도 초록 글씨 유지
|
|
|
|
e.CellStyle.BackColor = Color.White;
|
|
e.CellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |