# ============================================================================== # Enterprise Admin Studio - Main Entrypoint # PowerShell WPF Modern UI Infrastructure Management Tool # ============================================================================== [CmdletBinding()] param( [switch]$NoShow ) $ErrorActionPreference = 'Stop' $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path # 1. Load WPF / .NET GUI Assemblies Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase Add-Type -AssemblyName System.Data Add-Type -AssemblyName System.DirectoryServices # 2. Import Business Service Modules . (Join-Path $ScriptDir "Modules\ConfigService.ps1") . (Join-Path $ScriptDir "Modules\ADService.ps1") . (Join-Path $ScriptDir "Modules\NetAppService.ps1") . (Join-Path $ScriptDir "Modules\SqlService.ps1") # Global Configuration Object $global:AppConfig = Get-AppConfig # Helper: Load XAML File safely function Load-XamlElement { param([string]$Path) if (-not (Test-Path $Path)) { throw "XAML file not found: $Path" } $rawContent = [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8) $cleanContent = $rawContent -replace 'x:Class="[^"]*"', '' $stringReader = New-Object System.IO.StringReader($cleanContent) $xmlReader = [System.Xml.XmlReader]::Create($stringReader) try { return [System.Windows.Markup.XamlReader]::Load($xmlReader) } finally { $xmlReader.Close() $stringReader.Close() } } # 3. Initialize WPF Application & Global Theme Resources if ($null -eq [System.Windows.Application]::Current) { $app = New-Object System.Windows.Application } else { $app = [System.Windows.Application]::Current } $themePath = Join-Path $ScriptDir "Styles\Theme.xaml" $themeResource = Load-XamlElement -Path $themePath $app.Resources.MergedDictionaries.Add($themeResource) # 4. Load MainWindow XAML $mainXamlPath = Join-Path $ScriptDir "MainWindow.xaml" $mainWindow = Load-XamlElement -Path $mainXamlPath # Find Main Window Controls $mainContentHost = $mainWindow.FindName("MainContentHost") $txtHeaderTitle = $mainWindow.FindName("TxtHeaderTitle") $txtHeaderSubtitle = $mainWindow.FindName("TxtHeaderSubtitle") $txtSystemClock = $mainWindow.FindName("TxtSystemClock") $txtModeBadge = $mainWindow.FindName("TxtModeBadge") $badgeMode = $mainWindow.FindName("BadgeMode") $txtGlobalStatus = $mainWindow.FindName("TxtGlobalStatus") $dotGlobalStatus = $mainWindow.FindName("DotGlobalStatus") $navDashboard = $mainWindow.FindName("NavDashboard") $navAD = $mainWindow.FindName("NavAD") $navNetApp = $mainWindow.FindName("NavNetApp") $navSql = $mainWindow.FindName("NavSql") $navSettings = $mainWindow.FindName("NavSettings") $btnGlobalRefresh = $mainWindow.FindName("BtnGlobalRefresh") # Function to Update Mode Badge UI function Update-ModeBadgeUI { $isLive = ($global:AppConfig.Application.LiveMode -or $global:AppConfig.ActiveDirectory.UseLive -or $global:AppConfig.NetApp.UseLive -or $global:AppConfig.MsSql.UseLive) if ($txtModeBadge -and $badgeMode) { if ($isLive) { $txtModeBadge.Text = "실제 연결 모드 (Live)" $badgeMode.Background = [System.Windows.Media.BrushConverter]::new().ConvertFromString("#10B981") } else { $txtModeBadge.Text = "데모/모의 모드 (Mock)" $badgeMode.Background = [System.Windows.Media.BrushConverter]::new().ConvertFromString("#6366F1") } } } Update-ModeBadgeUI if ($txtSystemClock) { $txtSystemClock.Text = (Get-Date).ToString("yyyy-MM-dd HH:mm") } # View Cache $ViewCache = @{} # Function to Switch Navigation Pages function Navigate-ToPage { param([string]$PageName) switch ($PageName) { "Dashboard" { $txtHeaderTitle.Text = "통합 대시보드" $txtHeaderSubtitle.Text = "전체 인프라 실시간 상태 개요 및 최근 감사 로그" if (-not $ViewCache.ContainsKey("Dashboard")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\DashboardView.xaml") $activityGrid = $view.FindName("ActivityGrid") if ($activityGrid) { $activityLog = @( [PSCustomObject]@{ Timestamp = (Get-Date).AddMinutes(-3).ToString("HH:mm:ss"); TargetSystem = "Active Directory"; ActionType = "User Query"; Description = "LDAP 디렉터리 동기화 완료"; Status = "정상" }, [PSCustomObject]@{ Timestamp = (Get-Date).AddMinutes(-12).ToString("HH:mm:ss"); TargetSystem = "NetApp ONTAP"; ActionType = "Quota Check"; Description = "ONTAP REST API 헬스체크 통과"; Status = "정상" }, [PSCustomObject]@{ Timestamp = (Get-Date).AddMinutes(-35).ToString("HH:mm:ss"); TargetSystem = "MS SQL Server"; ActionType = "DB Inspection"; Description = "SQL Server 온라인 DB 카운트 정상 (16개)"; Status = "정상" }, [PSCustomObject]@{ Timestamp = (Get-Date).AddHours(-2).ToString("HH:mm:ss"); TargetSystem = "NetApp ONTAP"; ActionType = "Share Checked"; Description = "CIFS/NFS 공유 정책 감사 통과"; Status = "정상" } ) $activityGrid.ItemsSource = $activityLog } $quickAd = $view.FindName("QuickBtnAD") if ($quickAd) { $quickAd.add_Click({ $navAD.IsChecked = $true }) } $quickQuota = $view.FindName("QuickBtnQuota") if ($quickQuota) { $quickQuota.add_Click({ $navNetApp.IsChecked = $true }) } $quickShare = $view.FindName("QuickBtnShare") if ($quickShare) { $quickShare.add_Click({ $navNetApp.IsChecked = $true }) } $quickSql = $view.FindName("QuickBtnSql") if ($quickSql) { $quickSql.add_Click({ $navSql.IsChecked = $true }) } $ViewCache["Dashboard"] = $view } $mainContentHost.Content = $ViewCache["Dashboard"] } "AD" { $txtHeaderTitle.Text = "Active Directory 관리" $txtHeaderSubtitle.Text = "도메인 사용자 계정 관리, 상태 변경, 암호 초기화 및 그룹 할당" if (-not $ViewCache.ContainsKey("AD")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\ActiveDirectoryView.xaml") $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") $txtDetailOu = $view.FindName("TxtDetailOu") $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 ($grid) { $grid.add_SelectionChanged({ $selected = $grid.SelectedItem if ($selected) { $txtDetailName.Text = "$($selected.DisplayName) ($($selected.SamAccountName))" $txtDetailEmail.Text = $selected.UserPrincipalName $txtDetailOu.Text = $selected.DistinguishedName $txtPropStatus.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 } } }) } if ($btnUnlock) { $btnUnlock.add_Click({ $selected = $grid.SelectedItem 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 } else { [System.Windows.MessageBox]::Show("목록에서 잠금 해제할 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) } }) } if ($btnResetPwd) { $btnResetPwd.add_Click({ $selected = $grid.SelectedItem 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) } else { [System.Windows.MessageBox]::Show("목록에서 암호를 초기화할 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) } }) } if ($btnToggleEnable) { $btnToggleEnable.add_Click({ $selected = $grid.SelectedItem 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 } else { [System.Windows.MessageBox]::Show("목록에서 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) } }) } &$reloadAd $ViewCache["AD"] = $view } $mainContentHost.Content = $ViewCache["AD"] } "NetApp" { $txtHeaderTitle.Text = "NetApp 스토리지 관리" $txtHeaderSubtitle.Text = "ONTAP Qtree 생성, Quota 용량 정책 및 CIFS/NFS 공유 제어" 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") $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 ($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 }) } if ($btnNewShare) { $btnNewShare.add_Click({ $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 }) } &$reloadNetApp $ViewCache["NetApp"] = $view } $mainContentHost.Content = $ViewCache["NetApp"] } "Sql" { $txtHeaderTitle.Text = "MS SQL 데이터베이스 관리" $txtHeaderSubtitle.Text = "인스턴스 상태, 데이터베이스 모니터링, 백업/복원 및 빠른 쿼리 실행" if (-not $ViewCache.ContainsKey("Sql")) { $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\SqlDatabaseView.xaml") $dbGrid = $view.FindName("SqlDbGrid") $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 ($btnTestConn) { $btnTestConn.add_Click({ $res = Test-SqlConnection -Server $global:AppConfig.MsSql.Server -AuthType $global:AppConfig.MsSql.AuthType -Username $global:AppConfig.MsSql.Username -Password $global:AppConfig.MsSql.Password $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Error } [System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) }) } if ($btnBackup) { $btnBackup.add_Click({ $selected = $dbGrid.SelectedItem $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 }) } if ($btnExecQuery) { $btnExecQuery.add_Click({ $query = if ($txtSqlScript) { $txtSqlScript.Text } else { "SELECT 1" } $res = Invoke-SqlQuerySnippet -Query $query -Config $global:AppConfig if ($txtSqlOutput) { $txtSqlOutput.Text = $res.Output } }) } &$reloadSql $ViewCache["Sql"] = $view } $mainContentHost.Content = $ViewCache["Sql"] } "Settings" { $txtHeaderTitle.Text = "환경설정 및 실제 인프라 연결 구성" $txtHeaderSubtitle.Text = "인프라 모듈 접속 엔드포인트, 자격 증명 및 시스템 기본값 관리" 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") $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 # 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 $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) }) } # Test NetApp if ($btnTestNetApp) { $btnTestNetApp.add_Click({ $res = Test-NetAppConnection -ClusterEndpoint $txtNetAppEndpoint.Text -Username $txtNetAppUser.Text -Password $txtNetAppPassword.Password -IgnoreSsl $chkNetAppIgnoreSsl.IsChecked $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) }) } # 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 $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) }) } # 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 $report = @" === 전체 인프라 일괄 진단 결과 === [1] Active Directory (DC: $($txtAdDc.Text)) - 상태: $(if ($adRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($adRes.Message) [2] NetApp ONTAP REST API ($($txtNetAppEndpoint.Text)) - 상태: $(if ($netappRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($netappRes.Message) [3] MS SQL Server ($($txtSqlServer.Text)) - 상태: $(if ($sqlRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' }) - 세부: $($sqlRes.Message) "@ [System.Windows.MessageBox]::Show($report, "전체 인프라 연결 진단", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) }) } # Save Settings if ($btnSave) { $btnSave.add_Click({ $authType = if ($cmbSqlAuth.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } $newConfig = @{ Application = @{ LiveMode = [bool]$chkGlobalLive.IsChecked Theme = "Dark" AutoRefreshSeconds = 60 } ActiveDirectory = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkAdLive.IsChecked) DomainController = $txtAdDc.Text Domain = "corp.local" BaseOU = $txtAdBaseOu.Text UseLdaps = [bool]$chkAdLdaps.IsChecked Username = $txtAdUser.Text Password = $txtAdPassword.Password } NetApp = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkNetAppLive.IsChecked) ClusterEndpoint = $txtNetAppEndpoint.Text Port = 443 DefaultSvm = $txtNetAppDefaultSvm.Text Username = $txtNetAppUser.Text Password = $txtNetAppPassword.Password IgnoreSslErrors = [bool]$chkNetAppIgnoreSsl.IsChecked } MsSql = @{ UseLive = [bool]($chkGlobalLive.IsChecked -or $chkSqlLive.IsChecked) Server = $txtSqlServer.Text AuthType = $authType Username = $txtSqlUser.Text Password = $txtSqlPassword.Password DefaultDatabase = "master" ConnectionTimeout = 10 BackupDirectory = $txtSqlBackupDir.Text } } Save-AppConfig -Config $newConfig | Out-Null $global:AppConfig = Get-AppConfig Update-ModeBadgeUI # Invalidate view caches so next click loads fresh live data $ViewCache.Remove("AD") $ViewCache.Remove("NetApp") $ViewCache.Remove("Sql") [System.Windows.MessageBox]::Show("환경설정이 성공적으로 저장되었습니다.`n각 관리 화면에서 변경된 접속 정보가 즉시 적용됩니다.", "저장 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) }) } if ($btnReload) { $btnReload.add_Click({ $global:AppConfig = Get-AppConfig &$loadSettingsToUI Update-ModeBadgeUI [System.Windows.MessageBox]::Show("config.json 설정을 다시 불러왔습니다.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) }) } $ViewCache["Settings"] = $view } $mainContentHost.Content = $ViewCache["Settings"] } } } # Bind Navigation Radio Buttons $navDashboard.add_Checked({ Navigate-ToPage -PageName "Dashboard" }) $navAD.add_Checked({ Navigate-ToPage -PageName "AD" }) $navNetApp.add_Checked({ Navigate-ToPage -PageName "NetApp" }) $navSql.add_Checked({ Navigate-ToPage -PageName "Sql" }) $navSettings.add_Checked({ Navigate-ToPage -PageName "Settings" }) # Global Refresh Button if ($btnGlobalRefresh) { $btnGlobalRefresh.add_Click({ $currentPage = if ($navDashboard.IsChecked) { "Dashboard" } elseif ($navAD.IsChecked) { "AD" } elseif ($navNetApp.IsChecked) { "NetApp" } elseif ($navSql.IsChecked) { "Sql" } elseif ($navSettings.IsChecked) { "Settings" } else { "Dashboard" } $ViewCache.Remove($currentPage) Navigate-ToPage -PageName $currentPage }) } # Initial Page Load (Dashboard) Navigate-ToPage -PageName "Dashboard" # 5. Launch Window if (-not $NoShow) { Write-Host "Enterprise Admin Studio UI starting..." -ForegroundColor Cyan $mainWindow.ShowDialog() | Out-Null } else { Write-Host "Initialized successfully in headless mode." -ForegroundColor Green }