Files
ModernAdminStudio/Start-App.ps1
T

841 lines
41 KiB
PowerShell

# ==============================================================================
# Enterprise Admin Studio - Main Entrypoint
# PowerShell WPF Modern UI Infrastructure Management Tool
# ==============================================================================
[CmdletBinding()]
param(
[switch]$NoShow
)
$ErrorActionPreference = 'Stop'
$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
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")
. (Join-Path $ScriptDir "Modules\ShareSyncService.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")
$navShareAccess = $mainWindow.FindName("NavShareAccess")
$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 = @{}
# ==============================================================================
# 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)
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({ $navShareAccess.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")
$ViewCache["AD"] = $view
$grid = $view.FindName("AdUsersGrid")
$btnSearch = $view.FindName("BtnAdSearch")
$btnRefresh = $view.FindName("BtnAdRefresh")
$btnUnlock = $view.FindName("BtnAdUnlock")
$btnResetPwd = $view.FindName("BtnAdResetPwd")
$btnToggleEnable = $view.FindName("BtnAdToggleEnable")
$txtDetailName = $view.FindName("TxtDetailName")
$txtDetailEmail = $view.FindName("TxtDetailEmail")
$txtDetailOu = $view.FindName("TxtDetailOu")
$txtPropStatus = $view.FindName("TxtPropStatus")
$listGroups = $view.FindName("ListMemberGroups")
if ($btnSearch) { $btnSearch.add_Click({ & $script:reloadAd }) }
if ($btnRefresh) { $btnRefresh.add_Click({ & $script:reloadAd }) }
if ($grid) {
$grid.add_SelectionChanged({
$v = $ViewCache["AD"]
if (-not $v) { return }
$g = $v.FindName("AdUsersGrid")
$selected = if ($g) { $g.SelectedItem } else { $null }
if ($selected) {
$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))" }
$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
}
}
}
})
}
if ($btnUnlock) {
$btnUnlock.add_Click({
$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)
& $script:reloadAd
} else {
[System.Windows.MessageBox]::Show("목록에서 잠금 해제할 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
}
})
}
if ($btnResetPwd) {
$btnResetPwd.add_Click({
$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)
} else {
[System.Windows.MessageBox]::Show("목록에서 암호를 초기화할 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
}
})
}
if ($btnToggleEnable) {
$btnToggleEnable.add_Click({
$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)
& $script:reloadAd
} else {
[System.Windows.MessageBox]::Show("목록에서 사용자를 선택하십시오.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
}
})
}
& $script:reloadAd
}
$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")
$ViewCache["NetApp"] = $view
$btnRefreshStorage = $view.FindName("BtnRefreshStorage")
$btnNewQtree = $view.FindName("BtnNewQtree")
$btnNewShare = $view.FindName("BtnNewShare")
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)
& $script: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)
& $script:reloadNetApp
})
}
& $script:reloadNetApp
}
$mainContentHost.Content = $ViewCache["NetApp"]
}
"ShareAccess" {
$txtHeaderTitle.Text = "공유 권한 및 AD 보안그룹 연계 관리"
$txtHeaderSubtitle.Text = "DB 조회 기반의 사용자 권한 할당/해제 및 Share 동명 AD 보안그룹 1:1 동기화"
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")
$btnSelectAll = $view.FindName("BtnSelectAll")
$btnDeselectAll = $view.FindName("BtnDeselectAll")
$btnAdd = $view.FindName("BtnAddToShareGroup")
$btnRemove = $view.FindName("BtnRemoveFromShareGroup")
$btnClearLog = $view.FindName("BtnClearLog")
# Helper to append logs
$appendShareLog = {
param([string]$msg)
$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
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
}
$cmbShare.add_SelectionChanged({ & $script:reloadShareData })
}
if ($btnReloadDb) { $btnReloadDb.add_Click({ & $script:reloadShareData }) }
if ($btnReloadAd) { $btnReloadAd.add_Click({ & $script:reloadShareData }) }
# Select / Deselect All
if ($btnSelectAll) {
$btnSelectAll.add_Click({
$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({
$v = $ViewCache["ShareAccess"]
$g = $v.FindName("DbUsersGrid")
if ($g -and $g.ItemsSource) {
foreach ($u in $g.ItemsSource) { $u.IsSelected = $false }
$g.Items.Refresh()
}
})
}
# Action: Add Users to Share Group
if ($btnAdd) {
$btnAdd.add_Click({
$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 = 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
}
$sams = @($checkedUsers | ForEach-Object { $_.SamAccountName })
$res = Add-UsersToShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig
foreach ($l in $res.Logs) { & $appendShareLog $l }
[System.Windows.MessageBox]::Show($res.Message, "그룹 추가 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
& $script:reloadShareData
})
}
# Action: Remove Users from Share Group
if ($btnRemove) {
$btnRemove.add_Click({
$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 = 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
}
$sams = @($checkedUsers | ForEach-Object { $_.SamAccountName })
$res = Remove-UsersFromShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig
foreach ($l in $res.Logs) { & $appendShareLog $l }
[System.Windows.MessageBox]::Show($res.Message, "그룹 해제 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
& $script:reloadShareData
})
}
if ($btnClearLog) {
$btnClearLog.add_Click({
$v = $ViewCache["ShareAccess"]
$tl = $v.FindName("TxtSyncLogs")
if ($tl) { $tl.Text = "" }
})
}
# Initial Select
if ($cmbShare -and $cmbShare.Items.Count -gt 0) {
$cmbShare.SelectedIndex = 0
} else {
& $script:reloadShareData
}
}
$mainContentHost.Content = $ViewCache["ShareAccess"]
}
"Sql" {
$txtHeaderTitle.Text = "MS SQL 데이터베이스 관리"
$txtHeaderSubtitle.Text = "인스턴스 상태, 데이터베이스 모니터링, 백업/복원 및 빠른 쿼리 실행"
if (-not $ViewCache.ContainsKey("Sql")) {
$view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\SqlDatabaseView.xaml")
$ViewCache["Sql"] = $view
$btnSqlRefresh = $view.FindName("BtnSqlRefresh")
$btnTestConn = $view.FindName("BtnTestSqlConnection")
$btnBackup = $view.FindName("BtnBackupDb")
$btnExecQuery = $view.FindName("BtnExecuteQuery")
if ($btnSqlRefresh) { $btnSqlRefresh.add_Click({ & $script: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({
$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)
& $script:reloadSql
})
}
if ($btnExecQuery) {
$btnExecQuery.add_Click({
$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 ($to) { $to.Text = $res.Output }
})
}
& $script:reloadSql
}
$mainContentHost.Content = $ViewCache["Sql"]
}
"Settings" {
$txtHeaderTitle.Text = "환경설정 및 실제 인프라 연결 구성"
$txtHeaderSubtitle.Text = "인프라 모듈 접속 엔드포인트, 자격 증명 및 시스템 기본값 관리"
if (-not $ViewCache.ContainsKey("Settings")) {
$view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\SettingsView.xaml")
$ViewCache["Settings"] = $view
$btnTestAd = $view.FindName("BtnTestAd")
$btnTestNetApp = $view.FindName("BtnTestNetApp")
$btnTestSql = $view.FindName("BtnTestSql")
$btnTestAll = $view.FindName("BtnTestAllConnections")
$btnSave = $view.FindName("BtnSaveSettings")
$btnReload = $view.FindName("BtnReloadConfig")
& $script:loadSettingsToUI
# Test AD
if ($btnTestAd) {
$btnTestAd.add_Click({
$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)
})
}
# Test NetApp
if ($btnTestNetApp) {
$btnTestNetApp.add_Click({
$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)
})
}
# Test SQL
if ($btnTestSql) {
$btnTestSql.add_Click({
$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)
})
}
# Test All
if ($btnTestAll) {
$btnTestAll.add_Click({
$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: $dc)
- 상태: $(if ($adRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' })
- 세부: $($adRes.Message)
[2] NetApp ONTAP REST API ($ep)
- 상태: $(if ($netappRes.Success) { '√ 정상 연결' } else { 'X 연결 실패' })
- 세부: $($netappRes.Message)
[3] MS SQL Server ($srv)
- 상태: $(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({
$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
Theme = "Dark"
AutoRefreshSeconds = 60
}
ActiveDirectory = @{
UseLive = [bool]($chkGlobalLive.IsChecked -or $chkAdLive.IsChecked)
DomainController = $v.FindName("TxtAdDc").Text
Domain = "corp.local"
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 = $v.FindName("TxtNetAppEndpoint").Text
Port = 443
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 = $v.FindName("TxtSqlServer").Text
AuthType = $authType
Username = $v.FindName("TxtSqlUser").Text
Password = $v.FindName("TxtSqlPassword").Password
DefaultDatabase = "master"
ConnectionTimeout = 10
BackupDirectory = $v.FindName("TxtSqlBackupDir").Text
}
}
Save-AppConfig -Config $newConfig | Out-Null
$global:AppConfig = Get-AppConfig
Update-ModeBadgeUI
$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)
})
}
if ($btnReload) {
$btnReload.add_Click({
$global:AppConfig = Get-AppConfig
& $script:loadSettingsToUI
Update-ModeBadgeUI
[System.Windows.MessageBox]::Show("config.json 설정을 다시 불러왔습니다.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
})
}
}
$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" })
$navShareAccess.add_Checked({ Navigate-ToPage -PageName "ShareAccess" })
$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 ($navShareAccess.IsChecked) { "ShareAccess" }
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
}