From 7a614760b1c80f7878c1b313169701f53fcafe8d Mon Sep 17 00:00:00 2001 From: zumun Date: Tue, 20 Oct 2026 20:35:27 +0900 Subject: [PATCH] fix: resolve ShowDialog scope issue by refactoring event handlers to script-scoped ViewCache lookups --- Start-App.ps1 | 554 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 330 insertions(+), 224 deletions(-) diff --git a/Start-App.ps1 b/Start-App.ps1 index 1c0f177..57c2065 100644 --- a/Start-App.ps1 +++ b/Start-App.ps1 @@ -9,7 +9,13 @@ param( ) $ErrorActionPreference = 'Stop' -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ScriptDir = if ($PSScriptRoot) { + $PSScriptRoot +} elseif ($MyInvocation.MyCommand.Path) { + Split-Path -Parent $MyInvocation.MyCommand.Path +} else { + (Get-Location).Path +} # 1. Load WPF / .NET GUI Assemblies Add-Type -AssemblyName PresentationFramework @@ -111,6 +117,143 @@ if ($txtSystemClock) { # View Cache $ViewCache = @{} +# ============================================================================== +# Robust Script-Scoped Refresh Functions (Directly finds controls from ViewCache) +# ============================================================================== + +$script:reloadAd = { + $v = $ViewCache["AD"] + if (-not $v) { return } + $grid = $v.FindName("AdUsersGrid") + $txtSearch = $v.FindName("TxtAdSearch") + $txtStatusMsg = $v.FindName("TxtAdStatusMsg") + $txtTotalCount = $v.FindName("TxtAdTotalCount") + + $filter = if ($txtSearch) { $txtSearch.Text } else { "" } + $users = Get-ADUserList -Filter $filter -Config $global:AppConfig + if ($grid) { $grid.ItemsSource = $users } + if ($txtTotalCount) { $txtTotalCount.Text = "총 $($users.Count) 명의 사용자" } + if ($txtStatusMsg) { + $isLive = ($users.Count -gt 0 -and $users[0].IsLive) + $modeText = if ($isLive) { "실제 도메인 컨트롤러 연결 중 ($($global:AppConfig.ActiveDirectory.DomainController))" } else { "데모 시뮬레이션 모드 (config.json에서 실제 연결 설정 가능)" } + $txtStatusMsg.Text = "상태: $modeText" + } +} + +$script:reloadNetApp = { + $v = $ViewCache["NetApp"] + if (-not $v) { return } + $qtreeGrid = $v.FindName("QtreeGrid") + $quotaGrid = $v.FindName("QuotaGrid") + $shareGrid = $v.FindName("ShareGrid") + $txtNetAppStatus = $v.FindName("TxtNetAppStatusMsg") + + $qtrees = Get-NetAppQtrees -Config $global:AppConfig + $quotas = Get-NetAppQuotas -Config $global:AppConfig + $shares = Get-NetAppShares -Config $global:AppConfig + + if ($qtreeGrid) { $qtreeGrid.ItemsSource = $qtrees } + if ($quotaGrid) { $quotaGrid.ItemsSource = $quotas } + if ($shareGrid) { $shareGrid.ItemsSource = $shares } + + $isLive = ($qtrees.Count -gt 0 -and $qtrees[0].IsLive) + if ($txtNetAppStatus) { + $modeText = if ($isLive) { "ONTAP REST API 실시간 연결됨 ($($global:AppConfig.NetApp.ClusterEndpoint))" } else { "데모/시뮬레이션 모드 (ONTAP REST API v2 호환 구조)" } + $txtNetAppStatus.Text = "스토리지 응답: $modeText" + } +} + +$script:reloadShareData = { + $v = $ViewCache["ShareAccess"] + if (-not $v) { return } + $cmb = $v.FindName("CmbShareList") + $dbGrid = $v.FindName("DbUsersGrid") + $adGrid = $v.FindName("ADGroupMembersGrid") + $targetG = $v.FindName("TxtTargetADGroupName") + $dbCnt = $v.FindName("TxtDbCount") + $adCnt = $v.FindName("TxtAdGroupCount") + + $selectedItem = if ($cmb) { $cmb.SelectedItem } else { $null } + $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" } + if ($targetG) { $targetG.Text = $shareName } + + $dbUsers = Get-DbUsersForShare -ShareName $shareName -Config $global:AppConfig + if ($dbGrid) { $dbGrid.ItemsSource = $dbUsers } + if ($dbCnt) { $dbCnt.Text = "총 $($dbUsers.Count)명 조회됨" } + + $adMembers = Get-ADGroupMembersByShareName -GroupName $shareName -Config $global:AppConfig + if ($adGrid) { $adGrid.ItemsSource = $adMembers } + if ($adCnt) { $adCnt.Text = "현재 그룹 멤버: $($adMembers.Count)명" } +} + +$script:reloadSql = { + $v = $ViewCache["Sql"] + if (-not $v) { return } + $dbGrid = $v.FindName("SqlDbGrid") + $txtSqlStatusMsg = $v.FindName("TxtSqlStatusMsg") + $txtSqlTotalCount = $v.FindName("TxtSqlTotalCount") + + $dbs = Get-SqlDatabases -Config $global:AppConfig + if ($dbGrid) { $dbGrid.ItemsSource = $dbs } + if ($txtSqlTotalCount) { $txtSqlTotalCount.Text = "총 $($dbs.Count)개 데이터베이스" } + if ($txtSqlStatusMsg) { + $isLive = ($dbs.Count -gt 0 -and $dbs[0].IsLive) + $modeText = if ($isLive) { "실제 SQL Server 연결 ($($global:AppConfig.MsSql.Server))" } else { "데모/시뮬레이션 모드 (System.Data.SqlClient 연동 완비)" } + $txtSqlStatusMsg.Text = "상태: $modeText" + } +} + +$script:loadSettingsToUI = { + $v = $ViewCache["Settings"] + if (-not $v) { return } + + $chkGlobalLive = $v.FindName("ChkGlobalLiveMode") + $chkAdLive = $v.FindName("ChkAdLive") + $txtAdDc = $v.FindName("TxtAdDc") + $txtAdBaseOu = $v.FindName("TxtAdBaseOu") + $chkAdLdaps = $v.FindName("ChkAdLdaps") + $txtAdUser = $v.FindName("TxtAdUser") + $txtAdPassword = $v.FindName("TxtAdPassword") + + $chkNetAppLive = $v.FindName("ChkNetAppLive") + $txtNetAppEndpoint = $v.FindName("TxtNetAppEndpoint") + $txtNetAppDefaultSvm = $v.FindName("TxtNetAppDefaultSvm") + $chkNetAppIgnoreSsl = $v.FindName("ChkNetAppIgnoreSsl") + $txtNetAppUser = $v.FindName("TxtNetAppUser") + $txtNetAppPassword = $v.FindName("TxtNetAppPassword") + + $chkSqlLive = $v.FindName("ChkSqlLive") + $txtSqlServer = $v.FindName("TxtSqlServer") + $cmbSqlAuth = $v.FindName("CmbSqlAuthType") + $txtSqlBackupDir = $v.FindName("TxtSqlBackupDir") + $txtSqlUser = $v.FindName("TxtSqlUser") + $txtSqlPassword = $v.FindName("TxtSqlPassword") + + $c = $global:AppConfig + if ($chkGlobalLive) { $chkGlobalLive.IsChecked = [bool]$c.Application.LiveMode } + + if ($chkAdLive) { $chkAdLive.IsChecked = [bool]$c.ActiveDirectory.UseLive } + if ($txtAdDc) { $txtAdDc.Text = $c.ActiveDirectory.DomainController } + if ($txtAdBaseOu) { $txtAdBaseOu.Text = $c.ActiveDirectory.BaseOU } + if ($chkAdLdaps) { $chkAdLdaps.IsChecked = [bool]$c.ActiveDirectory.UseLdaps } + if ($txtAdUser) { $txtAdUser.Text = $c.ActiveDirectory.Username } + if ($txtAdPassword) { $txtAdPassword.Password = $c.ActiveDirectory.Password } + + if ($chkNetAppLive) { $chkNetAppLive.IsChecked = [bool]$c.NetApp.UseLive } + if ($txtNetAppEndpoint) { $txtNetAppEndpoint.Text = $c.NetApp.ClusterEndpoint } + if ($txtNetAppDefaultSvm) { $txtNetAppDefaultSvm.Text = $c.NetApp.DefaultSvm } + if ($chkNetAppIgnoreSsl) { $chkNetAppIgnoreSsl.IsChecked = [bool]$c.NetApp.IgnoreSslErrors } + if ($txtNetAppUser) { $txtNetAppUser.Text = $c.NetApp.Username } + if ($txtNetAppPassword) { $txtNetAppPassword.Password = $c.NetApp.Password } + + if ($chkSqlLive) { $chkSqlLive.IsChecked = [bool]$c.MsSql.UseLive } + if ($txtSqlServer) { $txtSqlServer.Text = $c.MsSql.Server } + if ($cmbSqlAuth) { $cmbSqlAuth.SelectedIndex = if ($c.MsSql.AuthType -eq "Sql") { 1 } else { 0 } } + if ($txtSqlBackupDir) { $txtSqlBackupDir.Text = $c.MsSql.BackupDirectory } + if ($txtSqlUser) { $txtSqlUser.Text = $c.MsSql.Username } + if ($txtSqlPassword) { $txtSqlPassword.Password = $c.MsSql.Password } +} + # Function to Switch Navigation Pages function Navigate-ToPage { param([string]$PageName) @@ -139,7 +282,7 @@ function Navigate-ToPage { $quickQuota = $view.FindName("QuickBtnQuota") if ($quickQuota) { $quickQuota.add_Click({ $navNetApp.IsChecked = $true }) } $quickShare = $view.FindName("QuickBtnShare") - if ($quickShare) { $quickShare.add_Click({ $navNetApp.IsChecked = $true }) } + if ($quickShare) { $quickShare.add_Click({ $navShareAccess.IsChecked = $true }) } $quickSql = $view.FindName("QuickBtnSql") if ($quickSql) { $quickSql.add_Click({ $navSql.IsChecked = $true }) } @@ -154,16 +297,14 @@ function Navigate-ToPage { if (-not $ViewCache.ContainsKey("AD")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\ActiveDirectoryView.xaml") - + $ViewCache["AD"] = $view + $grid = $view.FindName("AdUsersGrid") - $txtSearch = $view.FindName("TxtAdSearch") $btnSearch = $view.FindName("BtnAdSearch") $btnRefresh = $view.FindName("BtnAdRefresh") $btnUnlock = $view.FindName("BtnAdUnlock") $btnResetPwd = $view.FindName("BtnAdResetPwd") $btnToggleEnable = $view.FindName("BtnAdToggleEnable") - $txtStatusMsg = $view.FindName("TxtAdStatusMsg") - $txtTotalCount = $view.FindName("TxtAdTotalCount") $txtDetailName = $view.FindName("TxtDetailName") $txtDetailEmail = $view.FindName("TxtDetailEmail") @@ -171,36 +312,30 @@ function Navigate-ToPage { $txtPropStatus = $view.FindName("TxtPropStatus") $listGroups = $view.FindName("ListMemberGroups") - $reloadAd = { - $filter = if ($txtSearch) { $txtSearch.Text } else { "" } - $users = Get-ADUserList -Filter $filter -Config $global:AppConfig - $grid.ItemsSource = $users - if ($txtTotalCount) { $txtTotalCount.Text = "총 $($users.Count) 명의 사용자" } - if ($txtStatusMsg) { - $isLive = ($users.Count -gt 0 -and $users[0].IsLive) - $modeText = if ($isLive) { "실제 도메인 컨트롤러 연결 중 ($($global:AppConfig.ActiveDirectory.DomainController))" } else { "데모 시뮬레이션 모드 (config.json에서 실제 연결 설정 가능)" } - $txtStatusMsg.Text = "상태: $modeText" - } - } - - if ($btnSearch) { $btnSearch.add_Click($reloadAd) } - if ($btnRefresh) { $btnRefresh.add_Click($reloadAd) } + if ($btnSearch) { $btnSearch.add_Click({ & $script:reloadAd }) } + if ($btnRefresh) { $btnRefresh.add_Click({ & $script:reloadAd }) } if ($grid) { $grid.add_SelectionChanged({ - $selected = $grid.SelectedItem + $v = $ViewCache["AD"] + if (-not $v) { return } + $g = $v.FindName("AdUsersGrid") + $selected = if ($g) { $g.SelectedItem } else { $null } if ($selected) { - $txtDetailName.Text = "$($selected.DisplayName) ($($selected.SamAccountName))" - $txtDetailEmail.Text = $selected.UserPrincipalName - $txtDetailOu.Text = $selected.DistinguishedName - $txtPropStatus.Text = "$($selected.StatusText) (Locked: $($selected.LockedText))" + $dn = $v.FindName("TxtDetailName"); if ($dn) { $dn.Text = "$($selected.DisplayName) ($($selected.SamAccountName))" } + $de = $v.FindName("TxtDetailEmail"); if ($de) { $de.Text = $selected.UserPrincipalName } + $do = $v.FindName("TxtDetailOu"); if ($do) { $do.Text = $selected.DistinguishedName } + $ds = $v.FindName("TxtPropStatus"); if ($ds) { $ds.Text = "$($selected.StatusText) (Locked: $($selected.LockedText))" } - $listGroups.Items.Clear() - $listGroups.Items.Add("Domain Users") | Out-Null - $listGroups.Items.Add("Remote Desktop Users") | Out-Null - if ($selected.Department -like "*인프라*" -or $selected.Department -like "*시스템*") { - $listGroups.Items.Add("Storage-Admins") | Out-Null - $listGroups.Items.Add("Server-Operators") | Out-Null + $lg = $v.FindName("ListMemberGroups") + if ($lg) { + $lg.Items.Clear() + $lg.Items.Add("Domain Users") | Out-Null + $lg.Items.Add("Remote Desktop Users") | Out-Null + if ($selected.Department -like "*인프라*" -or $selected.Department -like "*시스템*") { + $lg.Items.Add("Storage-Admins") | Out-Null + $lg.Items.Add("Server-Operators") | Out-Null + } } } }) @@ -208,11 +343,13 @@ function Navigate-ToPage { if ($btnUnlock) { $btnUnlock.add_Click({ - $selected = $grid.SelectedItem + $v = $ViewCache["AD"] + $g = $v.FindName("AdUsersGrid") + $selected = if ($g) { $g.SelectedItem } else { $null } if ($selected) { $res = Unlock-ADUserAccount -SamAccountName $selected.SamAccountName -Config $global:AppConfig [System.Windows.MessageBox]::Show($res.Message, "AD 잠금 해제", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadAd + & $script:reloadAd } else { [System.Windows.MessageBox]::Show("목록에서 잠금 해제할 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) } @@ -221,7 +358,9 @@ function Navigate-ToPage { if ($btnResetPwd) { $btnResetPwd.add_Click({ - $selected = $grid.SelectedItem + $v = $ViewCache["AD"] + $g = $v.FindName("AdUsersGrid") + $selected = if ($g) { $g.SelectedItem } else { $null } if ($selected) { $res = Reset-ADUserPassword -SamAccountName $selected.SamAccountName -Config $global:AppConfig [System.Windows.MessageBox]::Show($res.Message, "AD 암호 초기화", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) @@ -233,20 +372,21 @@ function Navigate-ToPage { if ($btnToggleEnable) { $btnToggleEnable.add_Click({ - $selected = $grid.SelectedItem + $v = $ViewCache["AD"] + $g = $v.FindName("AdUsersGrid") + $selected = if ($g) { $g.SelectedItem } else { $null } if ($selected) { $newState = (-not $selected.Enabled) $res = Set-ADUserAccountState -SamAccountName $selected.SamAccountName -Enable $newState -Config $global:AppConfig [System.Windows.MessageBox]::Show($res.Message, "AD 계정 상태 전환", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadAd + & $script:reloadAd } else { [System.Windows.MessageBox]::Show("목록에서 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) } }) } - &$reloadAd - $ViewCache["AD"] = $view + & $script:reloadAd } $mainContentHost.Content = $ViewCache["AD"] } @@ -257,38 +397,19 @@ function Navigate-ToPage { if (-not $ViewCache.ContainsKey("NetApp")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\NetAppView.xaml") - - $qtreeGrid = $view.FindName("QtreeGrid") - $quotaGrid = $view.FindName("QuotaGrid") - $shareGrid = $view.FindName("ShareGrid") + $ViewCache["NetApp"] = $view + $btnRefreshStorage = $view.FindName("BtnRefreshStorage") $btnNewQtree = $view.FindName("BtnNewQtree") $btnNewShare = $view.FindName("BtnNewShare") - $txtNetAppStatus = $view.FindName("TxtNetAppStatusMsg") - $reloadNetApp = { - $qtrees = Get-NetAppQtrees -Config $global:AppConfig - $quotas = Get-NetAppQuotas -Config $global:AppConfig - $shares = Get-NetAppShares -Config $global:AppConfig - - if ($qtreeGrid) { $qtreeGrid.ItemsSource = $qtrees } - if ($quotaGrid) { $quotaGrid.ItemsSource = $quotas } - if ($shareGrid) { $shareGrid.ItemsSource = $shares } - - $isLive = ($qtrees.Count -gt 0 -and $qtrees[0].IsLive) - if ($txtNetAppStatus) { - $modeText = if ($isLive) { "ONTAP REST API 실시간 연결됨 ($($global:AppConfig.NetApp.ClusterEndpoint))" } else { "데모/시뮬레이션 모드 (ONTAP REST API v2 호환 구조)" } - $txtNetAppStatus.Text = "스토리지 응답: $modeText" - } - } - - if ($btnRefreshStorage) { $btnRefreshStorage.add_Click($reloadNetApp) } + if ($btnRefreshStorage) { $btnRefreshStorage.add_Click({ & $script:reloadNetApp }) } if ($btnNewQtree) { $btnNewQtree.add_Click({ $res = New-NetAppQtreeAction -Volume "vol_cifs_share01" -QtreeName "qtree_new_$((Get-Date).ToString('MMdd_HHmm'))" -Config $global:AppConfig [System.Windows.MessageBox]::Show($res.Message, "NetApp Qtree 생성", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadNetApp + & $script:reloadNetApp }) } @@ -297,12 +418,11 @@ function Navigate-ToPage { $shareName = "Share_$((Get-Date).ToString('MMdd_HHmm'))" $res = New-NetAppShareAction -ShareName $shareName -Path "/vol/vol_cifs_share01/$shareName" -Comment "관리 도구 생성 공유" -Config $global:AppConfig [System.Windows.MessageBox]::Show($res.Message, "NetApp CIFS 공유 생성", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadNetApp + & $script:reloadNetApp }) } - &$reloadNetApp - $ViewCache["NetApp"] = $view + & $script:reloadNetApp } $mainContentHost.Content = $ViewCache["NetApp"] } @@ -313,80 +433,68 @@ function Navigate-ToPage { if (-not $ViewCache.ContainsKey("ShareAccess")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\ShareAccessManagerView.xaml") + $ViewCache["ShareAccess"] = $view $cmbShare = $view.FindName("CmbShareList") $btnReloadDb = $view.FindName("BtnReloadFromDb") $btnReloadAd = $view.FindName("BtnReloadADGroup") - $txtTargetGroup = $view.FindName("TxtTargetADGroupName") - $dbGrid = $view.FindName("DbUsersGrid") $btnSelectAll = $view.FindName("BtnSelectAll") $btnDeselectAll = $view.FindName("BtnDeselectAll") - $txtDbCount = $view.FindName("TxtDbCount") $btnAdd = $view.FindName("BtnAddToShareGroup") $btnRemove = $view.FindName("BtnRemoveFromShareGroup") - - $adGrid = $view.FindName("ADGroupMembersGrid") - $txtAdCount = $view.FindName("TxtAdGroupCount") - - $txtLogs = $view.FindName("TxtSyncLogs") $btnClearLog = $view.FindName("BtnClearLog") # Helper to append logs - $appendLog = { + $appendShareLog = { param([string]$msg) - $time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - $txtLogs.Text = "[$time] $msg`r`n" + $txtLogs.Text + $v = $ViewCache["ShareAccess"] + if ($v) { + $tl = $v.FindName("TxtSyncLogs") + if ($tl) { + $time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + $tl.Text = "[$time] $msg`r`n" + $tl.Text + } + } } # Load Share List into ComboBox $shares = Get-DbShareList -Config $global:AppConfig - $cmbShare.Items.Clear() - foreach ($s in $shares) { - $item = New-Object System.Windows.Controls.ComboBoxItem - $item.Content = "$($s.ShareName) ($($s.Description))" - $item.Tag = $s.ShareName - $cmbShare.Items.Add($item) | Out-Null - } - if ($cmbShare.Items.Count -gt 0) { $cmbShare.SelectedIndex = 0 } + if ($cmbShare) { + $cmbShare.Items.Clear() + foreach ($s in $shares) { + $item = New-Object System.Windows.Controls.ComboBoxItem + $item.Content = "$($s.ShareName) ($($s.Description))" + $item.Tag = $s.ShareName + $cmbShare.Items.Add($item) | Out-Null + } - # Function to reload DB and AD members for current Share - $reloadShareData = { - $selectedItem = $cmbShare.SelectedItem - $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" } - if ($txtTargetGroup) { $txtTargetGroup.Text = $shareName } - - # 1. DB Users - $dbUsers = Get-DbUsersForShare -ShareName $shareName -Config $global:AppConfig - $dbGrid.ItemsSource = $dbUsers - if ($txtDbCount) { $txtDbCount.Text = "총 $($dbUsers.Count)명 조회됨" } - - # 2. AD Group Members - $adMembers = Get-ADGroupMembersByShareName -GroupName $shareName -Config $global:AppConfig - $adGrid.ItemsSource = $adMembers - if ($txtAdCount) { $txtAdCount.Text = "현재 그룹 멤버: $($adMembers.Count)명" } + $cmbShare.add_SelectionChanged({ & $script:reloadShareData }) } - $cmbShare.add_SelectionChanged({ &$reloadShareData }) - if ($btnReloadDb) { $btnReloadDb.add_Click({ &$reloadShareData }) } - if ($btnReloadAd) { $btnReloadAd.add_Click({ &$reloadShareData }) } + if ($btnReloadDb) { $btnReloadDb.add_Click({ & $script:reloadShareData }) } + if ($btnReloadAd) { $btnReloadAd.add_Click({ & $script:reloadShareData }) } # Select / Deselect All if ($btnSelectAll) { $btnSelectAll.add_Click({ - if ($dbGrid.ItemsSource) { - foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $true } - $dbGrid.Items.Refresh() + $v = $ViewCache["ShareAccess"] + $g = $v.FindName("DbUsersGrid") + if ($g -and $g.ItemsSource) { + foreach ($u in $g.ItemsSource) { $u.IsSelected = $true } + $g.Items.Refresh() } }) } if ($btnDeselectAll) { $btnDeselectAll.add_Click({ - if ($dbGrid.ItemsSource) { - foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $false } - $dbGrid.Items.Refresh() + $v = $ViewCache["ShareAccess"] + $g = $v.FindName("DbUsersGrid") + if ($g -and $g.ItemsSource) { + foreach ($u in $g.ItemsSource) { $u.IsSelected = $false } + $g.Items.Refresh() } }) } @@ -394,10 +502,14 @@ function Navigate-ToPage { # Action: Add Users to Share Group if ($btnAdd) { $btnAdd.add_Click({ - $selectedItem = $cmbShare.SelectedItem + $v = $ViewCache["ShareAccess"] + $cmb = $v.FindName("CmbShareList") + $g = $v.FindName("DbUsersGrid") + + $selectedItem = if ($cmb) { $cmb.SelectedItem } else { $null } $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" } - $checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected }) + $checkedUsers = if ($g -and $g.ItemsSource) { @($g.ItemsSource | Where-Object { $_.IsSelected }) } else { @() } if ($checkedUsers.Count -eq 0) { [System.Windows.MessageBox]::Show("목록에서 추가할 사용자를 체크박스로 1명 이상 선택하십시오.", "선택 필요", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) return @@ -406,20 +518,24 @@ function Navigate-ToPage { $sams = @($checkedUsers | ForEach-Object { $_.SamAccountName }) $res = Add-UsersToShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig - foreach ($l in $res.Logs) { &$appendLog $l } + foreach ($l in $res.Logs) { & $appendShareLog $l } [System.Windows.MessageBox]::Show($res.Message, "그룹 추가 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadShareData + & $script:reloadShareData }) } # Action: Remove Users from Share Group if ($btnRemove) { $btnRemove.add_Click({ - $selectedItem = $cmbShare.SelectedItem + $v = $ViewCache["ShareAccess"] + $cmb = $v.FindName("CmbShareList") + $g = $v.FindName("DbUsersGrid") + + $selectedItem = if ($cmb) { $cmb.SelectedItem } else { $null } $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" } - $checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected }) + $checkedUsers = if ($g -and $g.ItemsSource) { @($g.ItemsSource | Where-Object { $_.IsSelected }) } else { @() } if ($checkedUsers.Count -eq 0) { [System.Windows.MessageBox]::Show("목록에서 해제/제거할 사용자를 체크박스로 1명 이상 선택하십시오.", "선택 필요", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) return @@ -428,19 +544,27 @@ function Navigate-ToPage { $sams = @($checkedUsers | ForEach-Object { $_.SamAccountName }) $res = Remove-UsersFromShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig - foreach ($l in $res.Logs) { &$appendLog $l } + foreach ($l in $res.Logs) { & $appendShareLog $l } [System.Windows.MessageBox]::Show($res.Message, "그룹 해제 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) - &$reloadShareData + & $script:reloadShareData }) } if ($btnClearLog) { - $btnClearLog.add_Click({ $txtLogs.Text = "" }) + $btnClearLog.add_Click({ + $v = $ViewCache["ShareAccess"] + $tl = $v.FindName("TxtSyncLogs") + if ($tl) { $tl.Text = "" } + }) } - &$reloadShareData - $ViewCache["ShareAccess"] = $view + # Initial Select + if ($cmbShare -and $cmbShare.Items.Count -gt 0) { + $cmbShare.SelectedIndex = 0 + } else { + & $script:reloadShareData + } } $mainContentHost.Content = $ViewCache["ShareAccess"] } @@ -451,29 +575,14 @@ function Navigate-ToPage { if (-not $ViewCache.ContainsKey("Sql")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\SqlDatabaseView.xaml") - - $dbGrid = $view.FindName("SqlDbGrid") + $ViewCache["Sql"] = $view + $btnSqlRefresh = $view.FindName("BtnSqlRefresh") $btnTestConn = $view.FindName("BtnTestSqlConnection") $btnBackup = $view.FindName("BtnBackupDb") $btnExecQuery = $view.FindName("BtnExecuteQuery") - $txtSqlScript = $view.FindName("TxtSqlScript") - $txtSqlOutput = $view.FindName("TxtSqlOutput") - $txtSqlStatusMsg = $view.FindName("TxtSqlStatusMsg") - $txtSqlTotalCount = $view.FindName("TxtSqlTotalCount") - $reloadSql = { - $dbs = Get-SqlDatabases -Config $global:AppConfig - if ($dbGrid) { $dbGrid.ItemsSource = $dbs } - if ($txtSqlTotalCount) { $txtSqlTotalCount.Text = "총 $($dbs.Count)개 데이터베이스" } - if ($txtSqlStatusMsg) { - $isLive = ($dbs.Count -gt 0 -and $dbs[0].IsLive) - $modeText = if ($isLive) { "실제 SQL Server 연결 ($($global:AppConfig.MsSql.Server))" } else { "데모/시뮬레이션 모드 (System.Data.SqlClient 연동 완비)" } - $txtSqlStatusMsg.Text = "상태: $modeText" - } - } - - if ($btnSqlRefresh) { $btnSqlRefresh.add_Click($reloadSql) } + if ($btnSqlRefresh) { $btnSqlRefresh.add_Click({ & $script:reloadSql }) } if ($btnTestConn) { $btnTestConn.add_Click({ @@ -485,25 +594,29 @@ function Navigate-ToPage { if ($btnBackup) { $btnBackup.add_Click({ - $selected = $dbGrid.SelectedItem + $v = $ViewCache["Sql"] + $g = $v.FindName("SqlDbGrid") + $selected = if ($g) { $g.SelectedItem } else { $null } $dbName = if ($selected) { $selected.DatabaseName } else { "ERP_PRODUCTION" } $res = Invoke-SqlBackupAction -DatabaseName $dbName -Config $global:AppConfig $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Error } [System.Windows.MessageBox]::Show($res.Message, "SQL 백업 결과", [System.Windows.MessageBoxButton]::OK, $img) - &$reloadSql + & $script:reloadSql }) } if ($btnExecQuery) { $btnExecQuery.add_Click({ - $query = if ($txtSqlScript) { $txtSqlScript.Text } else { "SELECT 1" } + $v = $ViewCache["Sql"] + $ts = $v.FindName("TxtSqlScript") + $to = $v.FindName("TxtSqlOutput") + $query = if ($ts) { $ts.Text } else { "SELECT 1" } $res = Invoke-SqlQuerySnippet -Query $query -Config $global:AppConfig - if ($txtSqlOutput) { $txtSqlOutput.Text = $res.Output } + if ($to) { $to.Text = $res.Output } }) } - &$reloadSql - $ViewCache["Sql"] = $view + & $script:reloadSql } $mainContentHost.Content = $ViewCache["Sql"] } @@ -514,70 +627,28 @@ function Navigate-ToPage { if (-not $ViewCache.ContainsKey("Settings")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\SettingsView.xaml") - - # Controls - $chkGlobalLive = $view.FindName("ChkGlobalLiveMode") - $chkAdLive = $view.FindName("ChkAdLive") - $txtAdDc = $view.FindName("TxtAdDc") - $txtAdBaseOu = $view.FindName("TxtAdBaseOu") - $chkAdLdaps = $view.FindName("ChkAdLdaps") - $txtAdUser = $view.FindName("TxtAdUser") - $txtAdPassword = $view.FindName("TxtAdPassword") + $ViewCache["Settings"] = $view + $btnTestAd = $view.FindName("BtnTestAd") - - $chkNetAppLive = $view.FindName("ChkNetAppLive") - $txtNetAppEndpoint = $view.FindName("TxtNetAppEndpoint") - $txtNetAppDefaultSvm = $view.FindName("TxtNetAppDefaultSvm") - $chkNetAppIgnoreSsl = $view.FindName("ChkNetAppIgnoreSsl") - $txtNetAppUser = $view.FindName("TxtNetAppUser") - $txtNetAppPassword = $view.FindName("TxtNetAppPassword") $btnTestNetApp = $view.FindName("BtnTestNetApp") - - $chkSqlLive = $view.FindName("ChkSqlLive") - $txtSqlServer = $view.FindName("TxtSqlServer") - $cmbSqlAuth = $view.FindName("CmbSqlAuthType") - $txtSqlBackupDir = $view.FindName("TxtSqlBackupDir") - $txtSqlUser = $view.FindName("TxtSqlUser") - $txtSqlPassword = $view.FindName("TxtSqlPassword") $btnTestSql = $view.FindName("BtnTestSql") - $btnTestAll = $view.FindName("BtnTestAllConnections") $btnSave = $view.FindName("BtnSaveSettings") $btnReload = $view.FindName("BtnReloadConfig") - # Populate from Config - $loadSettingsToUI = { - $c = $global:AppConfig - if ($chkGlobalLive) { $chkGlobalLive.IsChecked = [bool]$c.Application.LiveMode } - - if ($chkAdLive) { $chkAdLive.IsChecked = [bool]$c.ActiveDirectory.UseLive } - if ($txtAdDc) { $txtAdDc.Text = $c.ActiveDirectory.DomainController } - if ($txtAdBaseOu) { $txtAdBaseOu.Text = $c.ActiveDirectory.BaseOU } - if ($chkAdLdaps) { $chkAdLdaps.IsChecked = [bool]$c.ActiveDirectory.UseLdaps } - if ($txtAdUser) { $txtAdUser.Text = $c.ActiveDirectory.Username } - if ($txtAdPassword) { $txtAdPassword.Password = $c.ActiveDirectory.Password } - - if ($chkNetAppLive) { $chkNetAppLive.IsChecked = [bool]$c.NetApp.UseLive } - if ($txtNetAppEndpoint) { $txtNetAppEndpoint.Text = $c.NetApp.ClusterEndpoint } - if ($txtNetAppDefaultSvm) { $txtNetAppDefaultSvm.Text = $c.NetApp.DefaultSvm } - if ($chkNetAppIgnoreSsl) { $chkNetAppIgnoreSsl.IsChecked = [bool]$c.NetApp.IgnoreSslErrors } - if ($txtNetAppUser) { $txtNetAppUser.Text = $c.NetApp.Username } - if ($txtNetAppPassword) { $txtNetAppPassword.Password = $c.NetApp.Password } - - if ($chkSqlLive) { $chkSqlLive.IsChecked = [bool]$c.MsSql.UseLive } - if ($txtSqlServer) { $txtSqlServer.Text = $c.MsSql.Server } - if ($cmbSqlAuth) { $cmbSqlAuth.SelectedIndex = if ($c.MsSql.AuthType -eq "Sql") { 1 } else { 0 } } - if ($txtSqlBackupDir) { $txtSqlBackupDir.Text = $c.MsSql.BackupDirectory } - if ($txtSqlUser) { $txtSqlUser.Text = $c.MsSql.Username } - if ($txtSqlPassword) { $txtSqlPassword.Password = $c.MsSql.Password } - } - - &$loadSettingsToUI + & $script:loadSettingsToUI # Test AD if ($btnTestAd) { $btnTestAd.add_Click({ - $res = Test-ADConnection -DomainController $txtAdDc.Text -BaseOU $txtAdBaseOu.Text -Username $txtAdUser.Text -Password $txtAdPassword.Password -UseLdaps $chkAdLdaps.IsChecked + $v = $ViewCache["Settings"] + $dc = $v.FindName("TxtAdDc").Text + $ou = $v.FindName("TxtAdBaseOu").Text + $user = $v.FindName("TxtAdUser").Text + $pass = $v.FindName("TxtAdPassword").Password + $ldaps = $v.FindName("ChkAdLdaps").IsChecked + + $res = Test-ADConnection -DomainController $dc -BaseOU $ou -Username $user -Password $pass -UseLdaps $ldaps $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Warning } [System.Windows.MessageBox]::Show($res.Message, "AD 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) }) @@ -586,7 +657,13 @@ function Navigate-ToPage { # Test NetApp if ($btnTestNetApp) { $btnTestNetApp.add_Click({ - $res = Test-NetAppConnection -ClusterEndpoint $txtNetAppEndpoint.Text -Username $txtNetAppUser.Text -Password $txtNetAppPassword.Password -IgnoreSsl $chkNetAppIgnoreSsl.IsChecked + $v = $ViewCache["Settings"] + $ep = $v.FindName("TxtNetAppEndpoint").Text + $user = $v.FindName("TxtNetAppUser").Text + $pass = $v.FindName("TxtNetAppPassword").Password + $ignoreSsl = $v.FindName("ChkNetAppIgnoreSsl").IsChecked + + $res = Test-NetAppConnection -ClusterEndpoint $ep -Username $user -Password $pass -IgnoreSsl $ignoreSsl $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Warning } [System.Windows.MessageBox]::Show($res.Message, "NetApp 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) }) @@ -595,8 +672,14 @@ function Navigate-ToPage { # Test SQL if ($btnTestSql) { $btnTestSql.add_Click({ - $authType = if ($cmbSqlAuth.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } - $res = Test-SqlConnection -Server $txtSqlServer.Text -AuthType $authType -Username $txtSqlUser.Text -Password $txtSqlPassword.Password + $v = $ViewCache["Settings"] + $srv = $v.FindName("TxtSqlServer").Text + $cmb = $v.FindName("CmbSqlAuthType") + $authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } + $user = $v.FindName("TxtSqlUser").Text + $pass = $v.FindName("TxtSqlPassword").Password + + $res = Test-SqlConnection -Server $srv -AuthType $authType -Username $user -Password $pass $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Warning } [System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) }) @@ -605,23 +688,40 @@ function Navigate-ToPage { # Test All if ($btnTestAll) { $btnTestAll.add_Click({ - $adRes = Test-ADConnection -DomainController $txtAdDc.Text -BaseOU $txtAdBaseOu.Text -Username $txtAdUser.Text -Password $txtAdPassword.Password -UseLdaps $chkAdLdaps.IsChecked - $netappRes = Test-NetAppConnection -ClusterEndpoint $txtNetAppEndpoint.Text -Username $txtNetAppUser.Text -Password $txtNetAppPassword.Password -IgnoreSsl $chkNetAppIgnoreSsl.IsChecked - $authType = if ($cmbSqlAuth.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } - $sqlRes = Test-SqlConnection -Server $txtSqlServer.Text -AuthType $authType -Username $txtSqlUser.Text -Password $txtSqlPassword.Password + $v = $ViewCache["Settings"] + $dc = $v.FindName("TxtAdDc").Text + $ou = $v.FindName("TxtAdBaseOu").Text + $user = $v.FindName("TxtAdUser").Text + $pass = $v.FindName("TxtAdPassword").Password + $ldaps = $v.FindName("ChkAdLdaps").IsChecked + + $ep = $v.FindName("TxtNetAppEndpoint").Text + $nUser = $v.FindName("TxtNetAppUser").Text + $nPass = $v.FindName("TxtNetAppPassword").Password + $ignoreSsl = $v.FindName("ChkNetAppIgnoreSsl").IsChecked + + $srv = $v.FindName("TxtSqlServer").Text + $cmb = $v.FindName("CmbSqlAuthType") + $authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } + $sUser = $v.FindName("TxtSqlUser").Text + $sPass = $v.FindName("TxtSqlPassword").Password + + $adRes = Test-ADConnection -DomainController $dc -BaseOU $ou -Username $user -Password $pass -UseLdaps $ldaps + $netappRes = Test-NetAppConnection -ClusterEndpoint $ep -Username $nUser -Password $nPass -IgnoreSsl $ignoreSsl + $sqlRes = Test-SqlConnection -Server $srv -AuthType $authType -Username $sUser -Password $sPass $report = @" === 전체 인프라 일괄 진단 결과 === -[1] Active Directory (DC: $($txtAdDc.Text)) +[1] Active Directory (DC: $dc) - 상태: $(if ($adRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($adRes.Message) -[2] NetApp ONTAP REST API ($($txtNetAppEndpoint.Text)) +[2] NetApp ONTAP REST API ($ep) - 상태: $(if ($netappRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($netappRes.Message) -[3] MS SQL Server ($($txtSqlServer.Text)) +[3] MS SQL Server ($srv) - 상태: $(if ($sqlRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($sqlRes.Message) "@ @@ -632,7 +732,15 @@ function Navigate-ToPage { # Save Settings if ($btnSave) { $btnSave.add_Click({ - $authType = if ($cmbSqlAuth.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } + $v = $ViewCache["Settings"] + $cmb = $v.FindName("CmbSqlAuthType") + $authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } + + $chkGlobalLive = $v.FindName("ChkGlobalLiveMode") + $chkAdLive = $v.FindName("ChkAdLive") + $chkNetAppLive = $v.FindName("ChkNetAppLive") + $chkSqlLive = $v.FindName("ChkSqlLive") + $newConfig = @{ Application = @{ LiveMode = [bool]$chkGlobalLive.IsChecked @@ -641,31 +749,31 @@ function Navigate-ToPage { } ActiveDirectory = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkAdLive.IsChecked) - DomainController = $txtAdDc.Text + DomainController = $v.FindName("TxtAdDc").Text Domain = "corp.local" - BaseOU = $txtAdBaseOu.Text - UseLdaps = [bool]$chkAdLdaps.IsChecked - Username = $txtAdUser.Text - Password = $txtAdPassword.Password + BaseOU = $v.FindName("TxtAdBaseOu").Text + UseLdaps = [bool]$v.FindName("ChkAdLdaps").IsChecked + Username = $v.FindName("TxtAdUser").Text + Password = $v.FindName("TxtAdPassword").Password } NetApp = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkNetAppLive.IsChecked) - ClusterEndpoint = $txtNetAppEndpoint.Text + ClusterEndpoint = $v.FindName("TxtNetAppEndpoint").Text Port = 443 - DefaultSvm = $txtNetAppDefaultSvm.Text - Username = $txtNetAppUser.Text - Password = $txtNetAppPassword.Password - IgnoreSslErrors = [bool]$chkNetAppIgnoreSsl.IsChecked + DefaultSvm = $v.FindName("TxtNetAppDefaultSvm").Text + Username = $v.FindName("TxtNetAppUser").Text + Password = $v.FindName("TxtNetAppPassword").Password + IgnoreSslErrors = [bool]$v.FindName("ChkNetAppIgnoreSsl").IsChecked } MsSql = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkSqlLive.IsChecked) - Server = $txtSqlServer.Text + Server = $v.FindName("TxtSqlServer").Text AuthType = $authType - Username = $txtSqlUser.Text - Password = $txtSqlPassword.Password + Username = $v.FindName("TxtSqlUser").Text + Password = $v.FindName("TxtSqlPassword").Password DefaultDatabase = "master" ConnectionTimeout = 10 - BackupDirectory = $txtSqlBackupDir.Text + BackupDirectory = $v.FindName("TxtSqlBackupDir").Text } } @@ -673,9 +781,9 @@ function Navigate-ToPage { $global:AppConfig = Get-AppConfig Update-ModeBadgeUI - # Invalidate view caches so next click loads fresh live data $ViewCache.Remove("AD") $ViewCache.Remove("NetApp") + $ViewCache.Remove("ShareAccess") $ViewCache.Remove("Sql") [System.Windows.MessageBox]::Show("환경설정이 성공적으로 저장되었습니다.`n각 관리 화면에서 변경된 접속 정보가 즉시 적용됩니다.", "저장 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) @@ -685,13 +793,11 @@ function Navigate-ToPage { if ($btnReload) { $btnReload.add_Click({ $global:AppConfig = Get-AppConfig - &$loadSettingsToUI + & $script:loadSettingsToUI Update-ModeBadgeUI [System.Windows.MessageBox]::Show("config.json 설정을 다시 불러왔습니다.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) }) } - - $ViewCache["Settings"] = $view } $mainContentHost.Content = $ViewCache["Settings"] }