JJServiceManagerEnt/Windows/SettingsWindow.cs

615 lines
29 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using JJServiceManager.Remote;
namespace JJServiceManager
{
/// <summary>설정 화면 (탭: 일반 / 원격 접속 허용 / 관리 대상 PC)</summary>
public sealed class SettingsWindow : Window
{
private readonly AppSettings _s;
// 일반
private readonly CheckBox _startup, _hotkey, _opLog, _shutdownButtons, _confirmLocal, _confirmRemote;
private readonly TextBox _hotkeyBox;
private readonly ComboBox _logMode, _esc;
private ModifierKeys _mods;
private Key _key;
// 원격 접속 허용
private readonly CheckBox _server, _activeListen, _allowView, _allowControl, _requireApproval;
private readonly ComboBox _serverMode;
private readonly TextBox _serverPort;
private readonly TextBlock _serverPortHint, _pwState, _serverInfo;
private readonly PasswordBox _pwd1, _pwd2;
private int _sshPort, _tcpPort;
private int _shownMode = -1;
// Passive 접속 (대상 PC 쪽)
private readonly ListBox _pmList;
private readonly TextBox _pmAddr, _pmCode, _pmPort;
private readonly ComboBox _pmMode;
private readonly TextBlock _pmResult;
private readonly ObservableCollection<PassiveManager> _pms = new ObservableCollection<PassiveManager>();
// 관리 대상 PC
private readonly CheckBox _manage;
private readonly ComboBox _passiveMode;
private readonly TextBox _passivePort;
private int _passiveSshPort, _passiveTcpPort;
private int _shownPassiveMode = -1;
private readonly ListBox _targets;
private readonly TextBox _addr, _hostname, _port, _code;
private readonly ComboBox _mode, _direction;
private readonly PasswordBox _pwdTarget;
private readonly TextBlock _pwTargetState, _test, _lblAddr;
private readonly FrameworkElement _lblMode, _lblPort, _lblCode, _pnlCode;
private readonly Button _btnTest;
private readonly ObservableCollection<TargetItem> _items = new ObservableCollection<TargetItem>();
/// <summary>목록 표시용</summary>
private sealed class TargetItem : ObservableObject
{
public RemoteTarget T { get; }
public TargetItem(RemoteTarget t) => T = t;
public string Label => T.Label;
public string ModeText => T.Direction == ConnectDirection.Passive
? "Passive · 코드 " + T.AccessCode
: "Active · " + (T.Mode == RemoteMode.Ssh ? "SSH" : "지정 포트") + " " + T.Port;
public void Changed()
{
OnPropertyChanged(nameof(Label));
OnPropertyChanged(nameof(ModeText));
}
}
public SettingsWindow(AppSettings current)
{
_s = current;
Title = "설정" + AppInstance.TitleTag;
Width = 800;
Height = 720;
MinHeight = 560;
ResizeMode = ResizeMode.CanResize;
FontFamily = new FontFamily("Malgun Gothic");
FontSize = 13;
Background = Brushes2.Make("#FAFAFA");
WindowStartupLocation = WindowStartupLocation.CenterOwner;
ShowInTaskbar = false;
AppIcon.Apply(this);
var root = XamlLoader.LoadElement("Settings.xaml");
Content = root;
// ───────── 일반 ─────────
_startup = root.Find<CheckBox>("chkStartup");
_hotkey = root.Find<CheckBox>("chkHotkey");
_opLog = root.Find<CheckBox>("chkOpLog");
_shutdownButtons = root.Find<CheckBox>("chkShutdownButtons");
_confirmLocal = root.Find<CheckBox>("chkConfirmLocal");
_confirmRemote = root.Find<CheckBox>("chkConfirmRemote");
_hotkeyBox = root.Find<TextBox>("txtHotkey");
_logMode = root.Find<ComboBox>("cmbLogMode");
_esc = root.Find<ComboBox>("cmbEsc");
root.Find<TextBlock>("txtDataPath").Text = "설정 파일: " + SettingsStore.SettingsPath
+ "\n작업 로그 파일: " + OperationLogFile.Folder;
_startup.IsChecked = current.RunAtStartup;
_hotkey.IsChecked = current.HotkeyEnabled;
_opLog.IsChecked = current.ShowOperationLog;
_shutdownButtons.IsChecked = current.ShowShutdownButtons;
_confirmLocal.IsChecked = current.ConfirmLocalStartStop;
_confirmRemote.IsChecked = current.ConfirmRemoteStartStop;
_logMode.SelectedIndex = (int)current.LogWindowMode;
_esc.SelectedIndex = (int)current.EscAction;
_mods = (ModifierKeys)current.HotkeyModifiers;
_key = (Key)current.HotkeyKey;
ShowHotkey();
// 단축키: 클릭 후 직접 눌러서 지정
_hotkeyBox.GotKeyboardFocus += (s, e) => _hotkeyBox.Background = Brushes2.Make("#FFF8E1");
_hotkeyBox.LostKeyboardFocus += (s, e) => { _hotkeyBox.Background = Brushes2.Make("#FAFAFA"); ShowHotkey(); };
_hotkeyBox.PreviewKeyDown += OnHotkeyKeyDown;
// ───────── 원격 접속 허용 ─────────
_server = root.Find<CheckBox>("chkServer");
_activeListen = root.Find<CheckBox>("chkActiveListen");
_allowView = root.Find<CheckBox>("chkAllowView");
_allowControl = root.Find<CheckBox>("chkAllowControl");
_requireApproval = root.Find<CheckBox>("chkRequireApproval");
_serverMode = root.Find<ComboBox>("cmbServerMode");
_serverPort = root.Find<TextBox>("txtServerPort");
_serverPortHint = root.Find<TextBlock>("txtServerPortHint");
_pwd1 = root.Find<PasswordBox>("pwdServer");
_pwd2 = root.Find<PasswordBox>("pwdServer2");
_pwState = root.Find<TextBlock>("txtPwState");
_serverInfo = root.Find<TextBlock>("txtServerInfo");
_sshPort = current.RemoteServerSshPort;
_tcpPort = current.RemoteServerTcpPort;
_server.IsChecked = current.RemoteServerEnabled;
_activeListen.IsChecked = current.RemoteServerActiveListen;
_allowView.IsChecked = current.RemoteAllowView;
_allowControl.IsChecked = current.RemoteAllowControl;
_requireApproval.IsChecked = current.RemoteRequireApproval;
_serverMode.SelectionChanged += (s, e) => OnServerModeChanged();
_serverMode.SelectedIndex = (int)current.RemoteServerMode;
_pwState.Text = string.IsNullOrEmpty(current.RemotePasswordHash)
? "비밀번호가 설정되어 있지 않습니다. 원격 접속을 허용하려면 입력하세요."
: "비밀번호가 설정되어 있습니다. 바꿀 때만 입력하세요.";
// 제어 허용은 화면 보기가 켜져 있어야 함
_allowView.Checked += (s, e) => _allowControl.IsEnabled = true;
_allowView.Unchecked += (s, e) => { _allowControl.IsChecked = false; _allowControl.IsEnabled = false; };
_allowControl.IsEnabled = _allowView.IsChecked == true;
UpdateServerInfo();
// Passive 접속 (이 PC → 관리 PC)
_pmList = root.Find<ListBox>("lstPassive");
_pmAddr = root.Find<TextBox>("txtPmAddr");
_pmCode = root.Find<TextBox>("txtPmCode");
_pmPort = root.Find<TextBox>("txtPmPort");
_pmMode = root.Find<ComboBox>("cmbPmMode");
_pmResult = root.Find<TextBlock>("txtPmResult");
foreach (var m in current.PassiveManagers) _pms.Add(m.Clone());
_pmList.ItemsSource = _pms;
_pmList.SelectionChanged += (s, e) => ShowPm(_pmList.SelectedItem as PassiveManager);
_pmMode.SelectionChanged += (s, e) =>
{
if (_pmPort.Text == "" || _pmPort.Text == "9990" || _pmPort.Text == "2224")
_pmPort.Text = _pmMode.SelectedIndex == 0 ? "2224" : "9990";
};
ClearPmForm();
root.Find<Button>("btnPmNew").Click += (s, e) => { _pmList.SelectedItem = null; ClearPmForm(); };
root.Find<Button>("btnPmAdd").Click += (s, e) => AddPm();
root.Find<Button>("btnPmUpdate").Click += (s, e) => UpdatePm();
root.Find<Button>("btnPmDelete").Click += (s, e) => DeletePm();
// ───────── 관리 대상 PC ─────────
_manage = root.Find<CheckBox>("chkManage");
_passiveMode = root.Find<ComboBox>("cmbPassiveMode");
_passivePort = root.Find<TextBox>("txtPassivePort");
_targets = root.Find<ListBox>("lstTargets");
_addr = root.Find<TextBox>("txtAddr");
_hostname = root.Find<TextBox>("txtHostname");
_port = root.Find<TextBox>("txtPort");
_code = root.Find<TextBox>("txtCode");
_mode = root.Find<ComboBox>("cmbMode");
_direction = root.Find<ComboBox>("cmbDirection");
_pwdTarget = root.Find<PasswordBox>("pwdTarget");
_pwTargetState = root.Find<TextBlock>("txtPwTarget");
_test = root.Find<TextBlock>("txtTest");
_lblAddr = root.Find<TextBlock>("lblAddr");
_lblMode = root.Find<FrameworkElement>("lblMode");
_lblPort = root.Find<FrameworkElement>("lblPort");
_lblCode = root.Find<FrameworkElement>("lblCode");
_pnlCode = root.Find<FrameworkElement>("pnlCode");
_btnTest = root.Find<Button>("btnTest");
_manage.IsChecked = current.RemoteManageEnabled;
_passiveSshPort = current.PassiveListenSshPort;
_passiveTcpPort = current.PassiveListenTcpPort;
_passiveMode.SelectionChanged += (s, e) =>
{
if (_shownPassiveMode == 0 && int.TryParse(_passivePort.Text, out var a)) _passiveSshPort = a;
if (_shownPassiveMode == 1 && int.TryParse(_passivePort.Text, out var b)) _passiveTcpPort = b;
_shownPassiveMode = _passiveMode.SelectedIndex;
_passivePort.Text = (_shownPassiveMode == 0 ? _passiveSshPort : _passiveTcpPort).ToString();
};
_passiveMode.SelectedIndex = (int)current.PassiveListenMode;
foreach (var t in current.Targets) _items.Add(new TargetItem(t.Clone()));
_targets.ItemsSource = _items;
_targets.SelectionChanged += (s, e) => ShowTarget(_targets.SelectedItem as TargetItem);
_mode.SelectionChanged += (s, e) =>
{
// 방식을 바꾸면 기본 포트로 (직접 바꾼 포트가 아닐 때)
if (_port.Text == "" || _port.Text == "9988" || _port.Text == "2222")
_port.Text = _mode.SelectedIndex == 0 ? "2222" : "9988";
};
_direction.SelectionChanged += (s, e) => OnDirectionChanged();
root.Find<Button>("btnNewCode").Click += (s, e) => _code.Text = RemoteTarget.NewAccessCode();
ClearForm();
root.Find<Button>("btnNew").Click += (s, e) => { _targets.SelectedItem = null; ClearForm(); };
root.Find<Button>("btnAddTarget").Click += (s, e) => AddTarget();
root.Find<Button>("btnUpdateTarget").Click += (s, e) => UpdateTarget();
root.Find<Button>("btnDeleteTarget").Click += (s, e) => DeleteTarget();
_btnTest.Click += async (s, e) => await TestTarget();
// ───────── 확인 / 취소 ─────────
root.Find<Button>("btnOk").Click += (s, e) => Save();
root.Find<Button>("btnCancel").Click += (s, e) => Close();
PreviewKeyDown += (s, e) =>
{
if (e.Key == Key.Escape && !_hotkeyBox.IsKeyboardFocused) Close();
};
}
// ───────────────────────── 단축키 ─────────────────────────
private void OnHotkeyKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
var key = e.Key == Key.System ? e.SystemKey : e.Key;
var mods = Keyboard.Modifiers;
if (Keyboard.IsKeyDown(Key.LWin) || Keyboard.IsKeyDown(Key.RWin)) mods |= ModifierKeys.Windows;
// 보조키만 누른 상태: 진행 중 표시
if (key is Key.LeftCtrl or Key.RightCtrl or Key.LeftAlt or Key.RightAlt
or Key.LeftShift or Key.RightShift or Key.LWin or Key.RWin)
{
_hotkeyBox.Text = HotkeyManager.Format(mods, Key.None) + "…";
return;
}
// Tab은 포커스 이동에 사용
if (key == Key.Tab && mods == ModifierKeys.None)
{
e.Handled = false;
return;
}
if ((mods & (ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows)) == 0)
{
_hotkeyBox.Text = "Ctrl / Alt / Win 중 하나와 함께 누르세요";
return;
}
_mods = mods;
_key = key;
ShowHotkey();
}
private void ShowHotkey() => _hotkeyBox.Text = HotkeyManager.Format(_mods, _key);
// ───────────────────────── 원격 접속 허용 ─────────────────────────
private void OnServerModeChanged()
{
// 방식마다 포트를 따로 기억
if (_shownMode == 0 && int.TryParse(_serverPort.Text, out var a)) _sshPort = a;
if (_shownMode == 1 && int.TryParse(_serverPort.Text, out var b)) _tcpPort = b;
_shownMode = _serverMode.SelectedIndex;
_serverPort.Text = (_shownMode == 0 ? _sshPort : _tcpPort).ToString();
_serverPortHint.Text = _shownMode == 0 ? "기본 2222" : "기본 9988";
UpdateServerInfo();
}
private void UpdateServerInfo()
{
var ips = NetInfo.LocalIPv4();
_serverInfo.Text =
$"이 PC: {Environment.MachineName} IP: {(ips.Count == 0 ? "(확인 안 됨)" : string.Join(", ", ips))}\n" +
"Active: 관리 PC의 [관리 대상 PC]에 위 IP, 같은 통신 방식과 포트, 접속 비밀번호를 입력합니다.\n" +
"Passive: 관리 PC에서 받은 접속 코드를 위 [Passive 접속]에 입력하고, 관리 PC에는 접속 비밀번호를 입력합니다.\n" +
"Active 수신을 켜면 Windows 방화벽에 해당 TCP 포트를 자동으로 열고, 끄면 규칙을 지웁니다.\n" +
"원격에서는 이 PC에 등록된 서비스만 보고 Start / Stop 할 수 있습니다.";
}
private void ClearPmForm()
{
_pmAddr.Text = "";
_pmCode.Text = "";
_pmMode.SelectedIndex = 1;
_pmPort.Text = "9990";
_pmResult.Text = "";
}
private void ShowPm(PassiveManager m)
{
if (m == null) return;
_pmAddr.Text = m.Address;
_pmCode.Text = m.AccessCode;
_pmMode.SelectedIndex = (int)m.Mode;
_pmPort.Text = m.Port.ToString();
_pmResult.Text = "";
}
private string ReadPm(out PassiveManager m)
{
m = new PassiveManager
{
Address = _pmAddr.Text.Trim(),
AccessCode = _pmCode.Text.Trim().ToUpperInvariant(),
Mode = (RemoteMode)Math.Max(0, _pmMode.SelectedIndex)
};
if (m.Address.Length == 0) return "관리 PC 주소를 입력하세요.";
if (m.AccessCode.Length < 4) return "관리 PC에서 받은 접속 코드를 입력하세요.";
if (!int.TryParse(_pmPort.Text.Trim(), out var p) || p < 1 || p > 65535) return "포트는 1~65535 사이 숫자로 입력하세요.";
m.Port = p;
return null;
}
private void AddPm()
{
var err = ReadPm(out var m);
if (err != null) { ShowResult(_pmResult, err, false); return; }
_pms.Add(m);
_pmList.SelectedItem = m;
ShowResult(_pmResult, "추가했습니다. [확인]을 누르면 저장됩니다.", true);
}
private void UpdatePm()
{
if (_pmList.SelectedItem is not PassiveManager sel) { ShowResult(_pmResult, "수정할 항목을 목록에서 고르세요.", false); return; }
var err = ReadPm(out var m);
if (err != null) { ShowResult(_pmResult, err, false); return; }
int i = _pms.IndexOf(sel);
m.Id = sel.Id;
_pms[i] = m;
_pmList.SelectedItem = m;
ShowResult(_pmResult, "수정했습니다. [확인]을 누르면 저장됩니다.", true);
}
private void DeletePm()
{
if (_pmList.SelectedItem is not PassiveManager sel) { ShowResult(_pmResult, "삭제할 항목을 목록에서 고르세요.", false); return; }
_pms.Remove(sel);
ClearPmForm();
ShowResult(_pmResult, "삭제했습니다. [확인]을 누르면 저장됩니다.", true);
}
// ───────────────────────── 관리 대상 PC ─────────────────────────
private bool FormIsPassive => _direction.SelectedIndex == 1;
private void OnDirectionChanged()
{
bool passive = FormIsPassive;
// Active: IP·방식·포트 입력 / Passive: 접속 코드
_lblAddr.Text = passive ? "대상 IP (자동)" : "대상 IP";
_addr.IsEnabled = !passive;
_mode.IsEnabled = !passive;
_port.IsEnabled = !passive;
_lblMode.Opacity = _lblPort.Opacity = passive ? 0.4 : 1;
_lblCode.Visibility = _pnlCode.Visibility = passive ? Visibility.Visible : Visibility.Collapsed;
_btnTest.IsEnabled = !passive;
if (passive && string.IsNullOrEmpty(_code.Text)) _code.Text = RemoteTarget.NewAccessCode();
}
private void ClearForm()
{
_direction.SelectedIndex = 0;
_addr.Text = "";
_hostname.Text = "";
_code.Text = "";
_mode.SelectedIndex = 1;
_port.Text = "9988";
_pwdTarget.Password = "";
_pwTargetState.Text = "대상 PC의 접속 비밀번호";
_test.Text = "";
OnDirectionChanged();
}
private void ShowTarget(TargetItem item)
{
if (item == null) return;
var t = item.T;
_direction.SelectedIndex = (int)t.Direction;
_addr.Text = t.Direction == ConnectDirection.Passive ? t.LastAddress : t.Address;
_hostname.Text = t.Hostname;
_code.Text = t.AccessCode;
_mode.SelectedIndex = (int)t.Mode;
_port.Text = t.Port.ToString();
_pwdTarget.Password = "";
_pwTargetState.Text = string.IsNullOrEmpty(t.ProtectedPassword) ? "비밀번호 없음 - 입력하세요" : "저장됨 (바꿀 때만 입력)";
_test.Text = "";
OnDirectionChanged();
}
/// <summary>입력값 검사. 문제가 있으면 메시지</summary>
private string ReadForm(out string addr, out RemoteMode mode, out int port)
{
addr = _addr.Text.Trim();
mode = (RemoteMode)Math.Max(0, _mode.SelectedIndex);
port = 0;
if (FormIsPassive)
{
if (_code.Text.Trim().Length < 4) return "[새 코드]를 눌러 접속 코드를 만드세요.";
int.TryParse(_port.Text.Trim(), out port);
return null;
}
if (addr.Length == 0) return "대상 IP를 입력하세요.";
if (addr.Contains(' ')) return "대상 IP에 공백이 있습니다.";
if (!int.TryParse(_port.Text.Trim(), out port) || port < 1 || port > 65535) return "포트는 1~65535 사이 숫자로 입력하세요.";
return null;
}
private void AddTarget()
{
var err = ReadForm(out var addr, out var mode, out var port);
if (err == null && _pwdTarget.Password.Length == 0) err = "비밀번호를 입력하세요.";
if (err == null && !FormIsPassive && _items.Any(i => i.T.Direction == ConnectDirection.Active && string.Equals(i.T.Address, addr, StringComparison.OrdinalIgnoreCase) && i.T.Port == port))
err = "같은 IP와 포트가 이미 등록되어 있습니다.";
if (err == null && FormIsPassive && _items.Any(i => string.Equals(i.T.AccessCode, _code.Text.Trim(), StringComparison.OrdinalIgnoreCase)))
err = "같은 접속 코드가 이미 있습니다. [새 코드]를 누르세요.";
if (err != null) { ShowTest(err, false); return; }
var t = new RemoteTarget
{
Direction = FormIsPassive ? ConnectDirection.Passive : ConnectDirection.Active,
AccessCode = FormIsPassive ? _code.Text.Trim() : "",
Address = FormIsPassive ? "" : addr,
Mode = mode,
Port = port > 0 ? port : 9988,
Hostname = _hostname.Text.Trim(),
ProtectedPassword = RemoteSecurity.Protect(_pwdTarget.Password)
};
var item = new TargetItem(t);
_items.Add(item);
_targets.SelectedItem = item;
ShowTest(FormIsPassive
? $"추가했습니다. 대상 PC의 [원격 접속 허용 → Passive 접속]에 이 PC 주소와 코드 {t.AccessCode}를 입력하세요."
: "추가했습니다. [확인]을 누르면 저장됩니다.", true);
}
private void UpdateTarget()
{
if (_targets.SelectedItem is not TargetItem item) { ShowTest("수정할 PC를 목록에서 고르세요.", false); return; }
var err = ReadForm(out var addr, out var mode, out var port);
if (err != null) { ShowTest(err, false); return; }
var t = item.T;
var newDir = FormIsPassive ? ConnectDirection.Passive : ConnectDirection.Active;
if (newDir != t.Direction || (newDir == ConnectDirection.Active && !string.Equals(t.Address, addr, StringComparison.OrdinalIgnoreCase)))
t.Hostname = ""; // 방향이나 IP가 바뀌면 hostname 다시 확인
t.Direction = newDir;
if (newDir == ConnectDirection.Passive)
{
t.AccessCode = _code.Text.Trim();
}
else
{
t.Address = addr;
t.Mode = mode;
t.Port = port;
}
if (_hostname.Text.Trim().Length > 0 && t.Hostname.Length == 0) t.Hostname = _hostname.Text.Trim();
if (_pwdTarget.Password.Length > 0) t.ProtectedPassword = RemoteSecurity.Protect(_pwdTarget.Password);
item.Changed();
ShowTarget(item);
ShowTest("수정했습니다. [확인]을 누르면 저장됩니다.", true);
}
private void DeleteTarget()
{
if (_targets.SelectedItem is not TargetItem item) { ShowTest("삭제할 PC를 목록에서 고르세요.", false); return; }
if (!Dialogs.Confirm(this, $"{item.Label} 을(를) 관리 대상에서 삭제할까요?", "삭제")) return;
_items.Remove(item);
ClearForm();
ShowTest("삭제했습니다. [확인]을 누르면 저장됩니다.", true);
}
private async System.Threading.Tasks.Task TestTarget()
{
var err = ReadForm(out var addr, out var mode, out var port);
if (err != null) { ShowTest(err, false); return; }
// 입력한 비밀번호가 없으면 저장된 비밀번호 사용
var pw = _pwdTarget.Password;
if (pw.Length == 0 && _targets.SelectedItem is TargetItem sel) pw = RemoteSecurity.Unprotect(sel.T.ProtectedPassword);
if (pw.Length == 0) { ShowTest("비밀번호를 입력하세요.", false); return; }
_btnTest.IsEnabled = false;
ShowTest($"{addr}:{port} 연결 중…", true);
try
{
var host = await RemoteClient.TestAsync(new RemoteTarget { Address = addr, Mode = mode, Port = port }, pw);
_hostname.Text = host;
if (_targets.SelectedItem is TargetItem item
&& string.Equals(item.T.Address, addr, StringComparison.OrdinalIgnoreCase))
{
item.T.Hostname = host;
item.Changed();
}
ShowTest($"연결 성공: {host}", true);
}
catch (Exception ex)
{
ShowTest("연결 실패: " + RemoteClient.Friendly(ex), false);
}
finally
{
_btnTest.IsEnabled = !FormIsPassive;
}
}
private void ShowTest(string msg, bool ok) => ShowResult(_test, msg, ok);
private static void ShowResult(TextBlock tb, string msg, bool ok)
{
tb.Text = msg;
tb.Foreground = ok ? Brushes2.Running : Brushes2.Stopped;
}
// ───────────────────────── 저장 ─────────────────────────
private void Save()
{
// 원격 접속 허용 검사
if (!int.TryParse(_serverPort.Text.Trim(), out var sp) || sp < 1 || sp > 65535)
{
Fail(1, "Active 수신 포트는 1~65535 사이 숫자로 입력하세요.");
return;
}
if (_serverMode.SelectedIndex == 0) _sshPort = sp; else _tcpPort = sp;
if (!int.TryParse(_passivePort.Text.Trim(), out var pp) || pp < 1 || pp > 65535)
{
Fail(2, "Passive 수신 포트는 1~65535 사이 숫자로 입력하세요.");
return;
}
if (_passiveMode.SelectedIndex == 0) _passiveSshPort = pp; else _passiveTcpPort = pp;
string newHash = null, newSalt = null;
if (_pwd1.Password.Length > 0 || _pwd2.Password.Length > 0)
{
if (_pwd1.Password != _pwd2.Password) { Fail(1, "접속 비밀번호와 확인이 다릅니다."); return; }
if (_pwd1.Password.Length < 4) { Fail(1, "접속 비밀번호는 4자 이상으로 입력하세요."); return; }
(newHash, newSalt) = RemoteSecurity.HashPassword(_pwd1.Password);
}
bool hasPassword = newHash != null || !string.IsNullOrEmpty(_s.RemotePasswordHash);
if (_server.IsChecked == true && !hasPassword)
{
Fail(1, "원격 접속을 허용하려면 접속 비밀번호를 정하세요.");
return;
}
if (_server.IsChecked == true && _activeListen.IsChecked != true && _pms.Count == 0)
{
Fail(1, "원격 접속 허용을 켜려면 Active 수신을 켜거나 Passive 접속을 하나 이상 추가하세요.");
return;
}
// 일반
_s.RunAtStartup = _startup.IsChecked == true;
_s.HotkeyEnabled = _hotkey.IsChecked == true;
_s.HotkeyModifiers = (int)_mods;
_s.HotkeyKey = (int)_key;
_s.ShowOperationLog = _opLog.IsChecked == true;
_s.ShowShutdownButtons = _shutdownButtons.IsChecked == true;
_s.ConfirmLocalStartStop = _confirmLocal.IsChecked == true;
_s.ConfirmRemoteStartStop = _confirmRemote.IsChecked == true;
_s.LogWindowMode = (LogWindowMode)Math.Max(0, _logMode.SelectedIndex);
_s.EscAction = (EscAction)Math.Max(0, _esc.SelectedIndex);
// 원격 접속 허용
_s.RemoteServerEnabled = _server.IsChecked == true;
_s.RemoteServerActiveListen = _activeListen.IsChecked == true;
_s.RemoteServerMode = (RemoteMode)Math.Max(0, _serverMode.SelectedIndex);
_s.RemoteServerSshPort = _sshPort;
_s.RemoteServerTcpPort = _tcpPort;
if (newHash != null)
{
_s.RemotePasswordHash = newHash;
_s.RemotePasswordSalt = newSalt;
}
_s.PassiveManagers = _pms.Select(m => m.Clone()).ToList();
_s.RemoteAllowView = _allowView.IsChecked == true;
_s.RemoteAllowControl = _allowView.IsChecked == true && _allowControl.IsChecked == true;
_s.RemoteRequireApproval = _requireApproval.IsChecked == true;
// 관리 대상 PC
_s.RemoteManageEnabled = _manage.IsChecked == true;
_s.PassiveListenMode = (RemoteMode)Math.Max(0, _passiveMode.SelectedIndex);
_s.PassiveListenSshPort = _passiveSshPort;
_s.PassiveListenTcpPort = _passiveTcpPort;
_s.Targets = _items.Select(i => i.T.Clone()).ToList();
DialogResult = true;
}
private void Fail(int tabIndex, string msg)
{
if (Content is FrameworkElement root && root.FindName("tabs") is TabControl tc) tc.SelectedIndex = tabIndex;
Dialogs.Error(this, msg, "설정");
}
}
}