feat: add Share Permission & AD Group sync UI framework and customizable service template

This commit is contained in:
2026-10-20 20:05:33 +09:00
parent ccc1b47dfd
commit a44c4a42cb
7 changed files with 604 additions and 3 deletions
+17
View File
@@ -1,5 +1,22 @@
# Enterprise Admin Studio - 변경 이력 (CHANGELOG.md) # Enterprise Admin Studio - 변경 이력 (CHANGELOG.md)
## [v1.3.0] - 2026-10-20
### Added
- **공유 권한 및 AD 보안그룹 1:1 연계 관리 (Share Permission & AD Group Sync) 워크플로우 틀 구축**:
- **신규 뷰 (`Views/ShareAccessManagerView.xaml`)**:
- 상단 Share 목록 드롭다운 및 DB/AD 새로고침
- 좌측: DB 조회 사용자 목록 (다중 선택 체크박스, SamAccountName, 이름, 부서, 상태)
- 중앙: `[그룹 추가]` / `[그룹 해제]` 대화형 액션 트리거 버튼
- 우측: Share와 동일한 이름의 AD 보안그룹 소속 멤버 실시간 조회 그리드
- 하단: 동기화 단계별 상세 실행 로그 및 결과 추적 콘솔
- **신규 서비스 스켈레톤 (`Modules/ShareSyncService.ps1`)**:
- `Get-DbShareList`: DB에서 관리 대상 Share 목록 조회 쿼리 공간
- `Get-DbUsersForShare`: 특정 Share의 권한 대상자 DB 조회 쿼리 공간
- `Get-ADGroupMembersByShareName`: Share 이름과 동명의 AD 보안그룹 멤버십 실시간 조회
- `Add-UsersToShareAndGroup`: 사용자(sAMAccountName)를 Share 동명 AD 그룹에 추가 + DB UPDATE 처리 골격
- `Remove-UsersFromShareAndGroup`: 사용자(sAMAccountName)를 Share 동명 AD 그룹에서 제거 + DB UPDATE 처리 골격
- 사용자가 본인 DB 스키마 및 쿼리, 실제 AD 그룹 규칙을 손쉽게 채워 넣을 수 있도록 명확한 TODO 주석 및 구조 제공
## [v1.2.0] - 2026-10-20 ## [v1.2.0] - 2026-10-20
### Added (신규 기능 추가) ### Added (신규 기능 추가)
- **실제 엔터프라이즈 인프라 연결 엔진 구현**: - **실제 엔터프라이즈 인프라 연결 엔진 구현**:
+9 -1
View File
@@ -74,7 +74,15 @@
</StackPanel> </StackPanel>
</RadioButton> </RadioButton>
<!-- 4. MS SQL Database --> <!-- 4. Share Permission & AD Group Sync -->
<RadioButton x:Name="NavShareAccess" Style="{StaticResource NavButtonStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE71E;" FontFamily="{StaticResource IconFont}" FontSize="16" Width="28" VerticalAlignment="Center" />
<TextBlock Text="공유 권한 및 AD 연계" VerticalAlignment="Center" />
</StackPanel>
</RadioButton>
<!-- 5. MS SQL Database -->
<RadioButton x:Name="NavSql" Style="{StaticResource NavButtonStyle}"> <RadioButton x:Name="NavSql" Style="{StaticResource NavButtonStyle}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="&#xEC92;" FontFamily="{StaticResource IconFont}" FontSize="16" Width="28" VerticalAlignment="Center" /> <TextBlock Text="&#xEC92;" FontFamily="{StaticResource IconFont}" FontSize="16" Width="28" VerticalAlignment="Center" />
+242
View File
@@ -0,0 +1,242 @@
# Modules\ShareSyncService.ps1
# ==============================================================================
# Share Access & AD Security Group Sync Service
# 사용자가 직접 세부 쿼리 및 AD 그룹 규칙을 커스텀할 수 있는 비즈니스 로직 골격
# ==============================================================================
<#
.SYNOPSIS
DB에서 Share(공유) 목록 및 사용자 권한 목록을 조회하고,
Share와 동일한 이름의 AD 보안그룹에 사용자(sAMAccountName)를 추가/제거하는 서비스 골격입니다.
#>
# 1. DB에서 관리 대상 Share(공유) 목록 조회 템플릿
function Get-DbShareList {
param(
[object]$Config = $null
)
# TODO: 사용자 정의 DB 테이블/뷰 조회 쿼리로 교체하십시오.
# 예: SELECT ShareName, SvmName, Description FROM tbl_Shares WHERE IsActive = 1
$customQuery = @"
SELECT
'Finance_Share' AS ShareName,
'재무기획팀 기밀문서' AS Description,
'vol_cifs_share01' AS VolumeName
UNION ALL
SELECT
'HR_Share' AS ShareName,
'인사팀 개인정보자료' AS Description,
'vol_cifs_share01' AS VolumeName
UNION ALL
SELECT
'DevOps_Share' AS ShareName,
'개발운영팀 공용폴더' AS Description,
'vol_user_homes' AS VolumeName
UNION ALL
SELECT
'Public_Projects' AS ShareName,
'전사 프로젝트 공유' AS Description,
'vol_user_homes' AS VolumeName;
"@
# 실제 DB 연결 모드일 경우 실행
$useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
if ($useLive) {
try {
$connStr = Build-SqlConnectionString -Server $Config.MsSql.Server -AuthType $Config.MsSql.AuthType -Username $Config.MsSql.Username -Password $Config.MsSql.Password
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = $customQuery
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$table = New-Object System.Data.DataTable
$adapter.Fill($table) | Out-Null
$conn.Close()
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($row in $table.Rows) {
$list.Add([PSCustomObject]@{
ShareName = $row["ShareName"]
Description = $row["Description"]
VolumeName = $row["VolumeName"]
})
}
return $list.ToArray()
} catch {
Write-Warning "DB Share 조회 실패: $($_.Exception.Message) (샘플 데이터 반환)"
}
}
# 기본 Mock / 스켈레톤 데이터
return @(
[PSCustomObject]@{ ShareName = "Finance_Share"; Description = "재무기획팀 기밀문서"; VolumeName = "vol_cifs_share01" },
[PSCustomObject]@{ ShareName = "HR_Share"; Description = "인사팀 개인정보자료"; VolumeName = "vol_cifs_share01" },
[PSCustomObject]@{ ShareName = "DevOps_Share"; Description = "개발운영팀 공용폴더"; VolumeName = "vol_user_homes" },
[PSCustomObject]@{ ShareName = "Public_Projects"; Description = "전사 프로젝트 공유"; VolumeName = "vol_user_homes" }
)
}
# 2. 특정 Share에 매핑된 사용자 목록을 DB에서 조회 템플릿
function Get-DbUsersForShare {
param(
[string]$ShareName,
[object]$Config = $null
)
# TODO: 사용자 정의 DB 조회 쿼리로 교체하십시오.
# 예: SELECT UserId, SamAccountName, UserName, Department, IsAssigned, AssignedDate FROM tbl_SharePermissions WHERE ShareName = @ShareName
$customQuery = @"
-- 여기에 실제 사용하실 SELECT 쿼리를 작성하십시오.
-- 파라미터: '$ShareName'
SELECT
'gdhong' AS SamAccountName, '홍길동' AS UserName, '클라우드인프라팀' AS Department, 1 AS IsAssigned, '2026-09-01' AS AssignedDate
UNION ALL
SELECT
'cskim' AS SamAccountName, '김철수' AS UserName, '데이터플랫폼팀' AS Department, 1 AS IsAssigned, '2026-09-10' AS AssignedDate
UNION ALL
SELECT
'yylee' AS SamAccountName, '이영희' AS UserName, '보안관제팀' AS Department, 0 AS IsAssigned, NULL AS AssignedDate
UNION ALL
SELECT
'mschoi' AS SamAccountName, '최민수' AS UserName, '개발운영팀' AS Department, 0 AS IsAssigned, NULL AS AssignedDate
UNION ALL
SELECT
'jhbak' AS SamAccountName, '박지훈' AS UserName, '시스템관리팀' AS Department, 1 AS IsAssigned, '2026-10-01' AS AssignedDate;
"@
# 목업 및 DB 결과 파싱
return @(
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "gdhong"; UserName = "홍길동"; Department = "클라우드인프라팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-09-01" },
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "cskim"; UserName = "김철수"; Department = "데이터플랫폼팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-09-10" },
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "yylee"; UserName = "이영희"; Department = "보안관제팀"; Status = "미할당 (해제됨)"; IsAssigned = $false; AssignedDate = "-" },
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "mschoi"; UserName = "최민수"; Department = "개발운영팀"; Status = "미할당 (해제됨)"; IsAssigned = $false; AssignedDate = "-" },
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "jhbak"; UserName = "박지훈"; Department = "시스템관리팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-10-01" }
)
}
# 3. Share와 동일한 이름의 AD 보안그룹 멤버 목록 조회
function Get-ADGroupMembersByShareName {
param(
[string]$GroupName, # Share 이름과 동일한 AD 그룹명
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
try {
$dc = $Config.ActiveDirectory.DomainController
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
$rootPath = "LDAP://$dc"
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
}
# 그룹 찾기
$searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
$searcher.Filter = "(&(objectCategory=group)(sAMAccountName=$GroupName))"
$groupResult = $searcher.FindOne()
if ($groupResult) {
$groupEntry = $groupResult.GetDirectoryEntry()
$members = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($memberDn in $groupEntry.Properties["member"]) {
# 멤버 DN에서 SamAccountName 추출 또는 파싱
$memberEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dc/$memberDn", $user, $pass)
$sam = $memberEntry.Properties["sAMAccountName"].Value
$disp = $memberEntry.Properties["displayName"].Value
$dept = $memberEntry.Properties["department"].Value
$members.Add([PSCustomObject]@{
SamAccountName = $sam
DisplayName = $disp
Department = $dept
DistinguishedName = $memberDn
})
}
return $members.ToArray()
}
} catch {
Write-Warning "실제 AD 그룹 조회 실패: $($_.Exception.Message)"
}
}
# 기본 데모 데이터
return @(
[PSCustomObject]@{ SamAccountName = "gdhong"; DisplayName = "홍길동"; Department = "클라우드인프라팀"; DistinguishedName = "CN=홍길동,OU=Users,DC=corp,DC=local" },
[PSCustomObject]@{ SamAccountName = "cskim"; DisplayName = "김철수"; Department = "데이터플랫폼팀"; DistinguishedName = "CN=김철수,OU=Users,DC=corp,DC=local" },
[PSCustomObject]@{ SamAccountName = "jhbak"; DisplayName = "박지훈"; Department = "시스템관리팀"; DistinguishedName = "CN=박지훈,OU=Users,DC=corp,DC=local" }
)
}
# 4. 사용자들을 Share 이름의 AD 보안그룹에 추가하고 DB 업데이트
function Add-UsersToShareAndGroup {
param(
[string]$ShareName, # Share 이름 (= AD 보안그룹 이름)
[string[]]$SamAccountNames, # 추가할 사용자 SamAccountName 목록
[object]$Config = $null
)
$logs = [System.Collections.Generic.List[string]]::new()
$logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에 사용자 추가 작업")
foreach ($sam in $SamAccountNames) {
# --- (A) AD 보안그룹에 사용자 객체 추가 로직 ---
# TODO: 사용자의 실제 AD 환경에 맞게 추가 구현 또는 보완하십시오.
# Live 모드일 경우:
# $groupEntry.Properties["member"].Add($userDn)
# $groupEntry.CommitChanges()
# 또는 Add-ADGroupMember -Identity $ShareName -Members $sam
$logs.Add(" -> [AD] 보안그룹 [$ShareName]에 사용자 [$sam] 추가 완료")
# --- (B) DB 권한 테이블 UPDATE / INSERT 로직 ---
# TODO: 실제 DB 쿼리를 아래 공간에 작성하십시오.
# 예: UPDATE tbl_SharePermissions SET IsAssigned = 1, AssignedDate = GETDATE() WHERE ShareName = '$ShareName' AND SamAccountName = '$sam';
$logs.Add(" -> [DB] Share 권한 테이블에 [$sam] 할당 상태 갱신 완료")
}
$logs.Add("[완료] 총 $($SamAccountNames.Count)명 사용자 추가 및 동기화 처리 완료")
return [PSCustomObject]@{
Success = $true
Logs = $logs.ToArray()
Message = "[$ShareName] 그룹 및 DB에 총 $($SamAccountNames.Count)명의 사용자가 추가되었습니다."
}
}
# 5. 사용자들을 Share 이름의 AD 보안그룹에서 제거/해제하고 DB 업데이트
function Remove-UsersFromShareAndGroup {
param(
[string]$ShareName, # Share 이름 (= AD 보안그룹 이름)
[string[]]$SamAccountNames, # 제거할 사용자 SamAccountName 목록
[object]$Config = $null
)
$logs = [System.Collections.Generic.List[string]]::new()
$logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에서 사용자 제거/해제 작업")
foreach ($sam in $SamAccountNames) {
# --- (A) AD 보안그룹에서 사용자 객체 제거 로직 ---
# TODO: 사용자의 실제 AD 환경에 맞게 추가 구현 또는 보완하십시오.
# Live 모드일 경우:
# $groupEntry.Properties["member"].Remove($userDn)
# $groupEntry.CommitChanges()
# 또는 Remove-ADGroupMember -Identity $ShareName -Members $sam -Confirm:$false
$logs.Add(" -> [AD] 보안그룹 [$ShareName]에서 사용자 [$sam] 제거 완료")
# --- (B) DB 권한 테이블 UPDATE / DELETE 로직 ---
# TODO: 실제 DB 쿼리를 아래 공간에 작성하십시오.
# 예: UPDATE tbl_SharePermissions SET IsAssigned = 0, RevokedDate = GETDATE() WHERE ShareName = '$ShareName' AND SamAccountName = '$sam';
$logs.Add(" -> [DB] Share 권한 테이블에서 [$sam] 해제 상태 갱신 완료")
}
$logs.Add("[완료] 총 $($SamAccountNames.Count)명 사용자 제거 및 동기화 처리 완료")
return [PSCustomObject]@{
Success = $true
Logs = $logs.ToArray()
Message = "[$ShareName] 그룹 및 DB에서 총 $($SamAccountNames.Count)명의 사용자가 제거(해제)되었습니다."
}
}
+3 -1
View File
@@ -54,6 +54,7 @@ ModernAdminStudio/
│ ├── DashboardView.xaml # 인프라 종합 현황, KPI 카드, 최근 감사 이력, 빠른 실행 │ ├── DashboardView.xaml # 인프라 종합 현황, KPI 카드, 최근 감사 이력, 빠른 실행
│ ├── ActiveDirectoryView.xaml # AD 사용자/그룹 목록, 계정 잠금 해제, 암호 초기화, 속성 패널 │ ├── ActiveDirectoryView.xaml # AD 사용자/그룹 목록, 계정 잠금 해제, 암호 초기화, 속성 패널
│ ├── NetAppView.xaml # SVM 선택, Qtree 관리, Quota 한도/사용률, CIFS/NFS 공유 제어 │ ├── NetAppView.xaml # SVM 선택, Qtree 관리, Quota 한도/사용률, CIFS/NFS 공유 제어
│ ├── ShareAccessManagerView.xaml # [신규] DB 조회 기반 사용자 선택/해제 및 Share 동명 AD 그룹 동기화
│ ├── SqlDatabaseView.xaml # SQL 인스턴스, DB 상태, 즉시 백업 트리거, 간이 T-SQL 콘솔 │ ├── SqlDatabaseView.xaml # SQL 인스턴스, DB 상태, 즉시 백업 트리거, 간이 T-SQL 콘솔
│ └── SettingsView.xaml # AD/스토리지/SQL 접속 엔드포인트 및 전체 연결 일괄 진단 │ └── SettingsView.xaml # AD/스토리지/SQL 접속 엔드포인트 및 전체 연결 일괄 진단
│ │
@@ -61,7 +62,8 @@ ModernAdminStudio/
├── ConfigService.ps1 # JSON 설정 로드/저장 관리 모듈 ├── ConfigService.ps1 # JSON 설정 로드/저장 관리 모듈
├── ADService.ps1 # Active Directory (.NET System.DirectoryServices) 모듈 ├── ADService.ps1 # Active Directory (.NET System.DirectoryServices) 모듈
├── NetAppService.ps1 # NetApp ONTAP REST API v2 연동 모듈 ├── NetAppService.ps1 # NetApp ONTAP REST API v2 연동 모듈
└── SqlService.ps1 # MS SQL Server (.NET System.Data.SqlClient) 모듈 ├── SqlService.ps1 # MS SQL Server (.NET System.Data.SqlClient) 모듈
└── ShareSyncService.ps1 # [신규] DB Share 조회 및 AD 동명 보안그룹 연계 모듈 (커스텀 전용 틀)
``` ```
--- ---
+142
View File
@@ -23,6 +23,7 @@ Add-Type -AssemblyName System.DirectoryServices
. (Join-Path $ScriptDir "Modules\ADService.ps1") . (Join-Path $ScriptDir "Modules\ADService.ps1")
. (Join-Path $ScriptDir "Modules\NetAppService.ps1") . (Join-Path $ScriptDir "Modules\NetAppService.ps1")
. (Join-Path $ScriptDir "Modules\SqlService.ps1") . (Join-Path $ScriptDir "Modules\SqlService.ps1")
. (Join-Path $ScriptDir "Modules\ShareSyncService.ps1")
# Global Configuration Object # Global Configuration Object
$global:AppConfig = Get-AppConfig $global:AppConfig = Get-AppConfig
@@ -78,6 +79,7 @@ $dotGlobalStatus = $mainWindow.FindName("DotGlobalStatus")
$navDashboard = $mainWindow.FindName("NavDashboard") $navDashboard = $mainWindow.FindName("NavDashboard")
$navAD = $mainWindow.FindName("NavAD") $navAD = $mainWindow.FindName("NavAD")
$navNetApp = $mainWindow.FindName("NavNetApp") $navNetApp = $mainWindow.FindName("NavNetApp")
$navShareAccess = $mainWindow.FindName("NavShareAccess")
$navSql = $mainWindow.FindName("NavSql") $navSql = $mainWindow.FindName("NavSql")
$navSettings = $mainWindow.FindName("NavSettings") $navSettings = $mainWindow.FindName("NavSettings")
$btnGlobalRefresh = $mainWindow.FindName("BtnGlobalRefresh") $btnGlobalRefresh = $mainWindow.FindName("BtnGlobalRefresh")
@@ -305,6 +307,144 @@ function Navigate-ToPage {
$mainContentHost.Content = $ViewCache["NetApp"] $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")
$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 = {
param([string]$msg)
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$txtLogs.Text = "[$time] $msg`r`n" + $txtLogs.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 }
# 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({ &$reloadShareData })
if ($btnReloadDb) { $btnReloadDb.add_Click({ &$reloadShareData }) }
if ($btnReloadAd) { $btnReloadAd.add_Click({ &$reloadShareData }) }
# Select / Deselect All
if ($btnSelectAll) {
$btnSelectAll.add_Click({
if ($dbGrid.ItemsSource) {
foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $true }
$dbGrid.Items.Refresh()
}
})
}
if ($btnDeselectAll) {
$btnDeselectAll.add_Click({
if ($dbGrid.ItemsSource) {
foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $false }
$dbGrid.Items.Refresh()
}
})
}
# Action: Add Users to Share Group
if ($btnAdd) {
$btnAdd.add_Click({
$selectedItem = $cmbShare.SelectedItem
$shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" }
$checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected })
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) { &$appendLog $l }
[System.Windows.MessageBox]::Show($res.Message, "그룹 추가 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
&$reloadShareData
})
}
# Action: Remove Users from Share Group
if ($btnRemove) {
$btnRemove.add_Click({
$selectedItem = $cmbShare.SelectedItem
$shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" }
$checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected })
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) { &$appendLog $l }
[System.Windows.MessageBox]::Show($res.Message, "그룹 해제 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
&$reloadShareData
})
}
if ($btnClearLog) {
$btnClearLog.add_Click({ $txtLogs.Text = "" })
}
&$reloadShareData
$ViewCache["ShareAccess"] = $view
}
$mainContentHost.Content = $ViewCache["ShareAccess"]
}
"Sql" { "Sql" {
$txtHeaderTitle.Text = "MS SQL 데이터베이스 관리" $txtHeaderTitle.Text = "MS SQL 데이터베이스 관리"
$txtHeaderSubtitle.Text = "인스턴스 상태, 데이터베이스 모니터링, 백업/복원 및 빠른 쿼리 실행" $txtHeaderSubtitle.Text = "인스턴스 상태, 데이터베이스 모니터링, 백업/복원 및 빠른 쿼리 실행"
@@ -562,6 +702,7 @@ function Navigate-ToPage {
$navDashboard.add_Checked({ Navigate-ToPage -PageName "Dashboard" }) $navDashboard.add_Checked({ Navigate-ToPage -PageName "Dashboard" })
$navAD.add_Checked({ Navigate-ToPage -PageName "AD" }) $navAD.add_Checked({ Navigate-ToPage -PageName "AD" })
$navNetApp.add_Checked({ Navigate-ToPage -PageName "NetApp" }) $navNetApp.add_Checked({ Navigate-ToPage -PageName "NetApp" })
$navShareAccess.add_Checked({ Navigate-ToPage -PageName "ShareAccess" })
$navSql.add_Checked({ Navigate-ToPage -PageName "Sql" }) $navSql.add_Checked({ Navigate-ToPage -PageName "Sql" })
$navSettings.add_Checked({ Navigate-ToPage -PageName "Settings" }) $navSettings.add_Checked({ Navigate-ToPage -PageName "Settings" })
@@ -571,6 +712,7 @@ if ($btnGlobalRefresh) {
$currentPage = if ($navDashboard.IsChecked) { "Dashboard" } $currentPage = if ($navDashboard.IsChecked) { "Dashboard" }
elseif ($navAD.IsChecked) { "AD" } elseif ($navAD.IsChecked) { "AD" }
elseif ($navNetApp.IsChecked) { "NetApp" } elseif ($navNetApp.IsChecked) { "NetApp" }
elseif ($navShareAccess.IsChecked) { "ShareAccess" }
elseif ($navSql.IsChecked) { "Sql" } elseif ($navSql.IsChecked) { "Sql" }
elseif ($navSettings.IsChecked) { "Settings" } elseif ($navSettings.IsChecked) { "Settings" }
else { "Dashboard" } else { "Dashboard" }
+20 -1
View File
@@ -73,7 +73,26 @@ pwsh -ExecutionPolicy Bypass -File .\Start-App.ps1
- 활성화된 Windows SMB (CIFS) 및 Linux NFS 공유 목록, 마운트 경로, 권한 요약을 확인합니다. - 활성화된 Windows SMB (CIFS) 및 Linux NFS 공유 목록, 마운트 경로, 권한 요약을 확인합니다.
- `+ 신규 공유 생성` 버튼을 통해 공유 이름과 경로를 지정하여 신규 파일 공유를 생성합니다. - `+ 신규 공유 생성` 버튼을 통해 공유 이름과 경로를 지정하여 신규 파일 공유를 생성합니다.
### 4.4 MS SQL 데이터베이스 관리 ### 4.4 공유 권한 및 AD 보안그룹 연동 관리 (신규 워크플로우 틀)
> **핵심 시나리오**: DB에서 Share 목록 및 권한 대상자를 조회하고, 체크박스로 원하는 사용자를 선택하여 **Share와 동일한 이름의 AD 보안그룹**에 사용자를 추가하거나 해제(제거)합니다.
1. **상단 Share 선택**:
- `관리 대상 Share (공유)` 콤보박스에서 작업할 공유(예: `Finance_Share`, `HR_Share`)를 선택합니다.
- 선택 즉시 우측의 `연계 보안그룹` 배지가 해당 Share 이름으로 자동 변경됩니다.
2. **좌측: DB 사용자 목록 확인 및 선택**:
- DB에서 조회된 사용자 목록을 확인하고, 처리할 사용자를 **체크박스**로 1명 이상 선택합니다. (상단의 `전체선택` / `선택해제` 버튼 활용 가능)
3. **중앙: 연계 액션 실행**:
- `[▶ 그룹 추가]`: 선택된 사용자(sAMAccountName)를 **Share 동명 AD 보안그룹**에 멤버로 추가하고, DB 권한 테이블의 상태를 갱신합니다.
- `[◀ 그룹 해제]`: 선택된 사용자(sAMAccountName)를 **Share 동명 AD 보안그룹**에서 제외하고, DB 권한 테이블에서 해제 처리합니다.
4. **우측 & 하단: 검증 및 로그**:
- 우측 그리드에서 실제 AD 보안그룹 멤버십이 실시간 갱신되는 것을 확인합니다.
- 하단 콘솔 창에서 각 사용자별 처리 단계(`[AD] 추가 완료`, `[DB] 갱신 완료`)의 세부 로그를 확인할 수 있습니다.
> [!TIP]
> **직접 세부 로직을 채워 넣는 위치**:
> `Modules/ShareSyncService.ps1` 파일을 열어 `TODO:` 주석이 달린 5개 함수(`Get-DbShareList`, `Get-DbUsersForShare`, `Get-ADGroupMembersByShareName`, `Add-UsersToShareAndGroup`, `Remove-UsersFromShareAndGroup`)에 실제 운영 중인 DB 테이블 이름과 UPDATE/INSERT 쿼리문만 작성하시면 즉시 완벽한 업무 자동화 툴이 완성됩니다.
### 4.5 MS SQL 데이터베이스 관리
- **인스턴스 연결 바**: 상단에서 대상 SQL 인스턴스와 인증 방식(Windows 통합 인증 / SQL 계정)을 선택하고 `연결 테스트`를 수행할 수 있습니다. - **인스턴스 연결 바**: 상단에서 대상 SQL 인스턴스와 인증 방식(Windows 통합 인증 / SQL 계정)을 선택하고 `연결 테스트`를 수행할 수 있습니다.
- **데이터베이스 목록**: - **데이터베이스 목록**:
- 인스턴스 내 모든 데이터베이스의 온라인 상태, 크기(MB), 복구 모델(FULL/SIMPLE), 데이터 정렬(Collation), 최근 Full 백업 일시를 실시간 파악합니다. - 인스턴스 내 모든 데이터베이스의 온라인 상태, 크기(MB), 복구 모델(FULL/SIMPLE), 데이터 정렬(Collation), 최근 Full 백업 일시를 실시간 파악합니다.
+171
View File
@@ -0,0 +1,171 @@
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="24">
<Grid.RowDefinitions>
<!-- 0. Header -->
<RowDefinition Height="Auto" />
<!-- 1. Top Share & DB Selector Bar -->
<RowDefinition Height="Auto" />
<!-- 2. Main Dual Grid & Action Center -->
<RowDefinition Height="*" />
<!-- 3. Bottom Execution Log & Status -->
<RowDefinition Height="180" />
</Grid.RowDefinitions>
<!-- 0. Header -->
<StackPanel Grid.Row="0" Margin="0,0,0,18">
<TextBlock Text="공유 권한 및 AD 보안그룹 연동 관리" FontSize="24" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}" />
<TextBlock Text="DB에서 Share 대상자를 조회하여 선택적으로 권한을 부여/해제하고, Share 이름과 동일한 AD 보안그룹 멤버십을 1:1 동기화합니다."
FontSize="13" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,4,0,0" />
</StackPanel>
<!-- 1. Top Selector Bar -->
<Border Grid.Row="1" Style="{StaticResource ModernCardStyle}" Margin="0,0,0,16" Padding="14">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="260" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="관리 대상 Share (공유):" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center" Margin="0,0,10,0" />
<ComboBox Grid.Column="1" x:Name="CmbShareList" SelectedIndex="0" Height="32" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,16,0" />
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button x:Name="BtnReloadFromDb" Content="&#xE72C; DB 데이터 조회" Style="{StaticResource PrimaryButtonStyle}" Margin="0,0,8,0" />
<Button x:Name="BtnReloadADGroup" Content="AD 그룹 멤버 새로고침" Style="{StaticResource SecondaryButtonStyle}" />
</StackPanel>
<!-- Mapping Rule Indicator -->
<StackPanel Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Border Background="#1E293B" CornerRadius="6" Padding="10,6" BorderBrush="{StaticResource CardBorderBrush}" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE71E;" FontFamily="{StaticResource IconFont}" Foreground="#60A5FA" Margin="0,0,6,0" VerticalAlignment="Center" />
<TextBlock Text="연계 보안그룹: " Foreground="{StaticResource TextSecondaryBrush}" FontSize="12" />
<TextBlock x:Name="TxtTargetADGroupName" Text="Finance_Share" FontWeight="Bold" Foreground="#60A5FA" FontSize="12" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Border>
<!-- 2. Main Dual Grid & Action Center -->
<Grid Grid.Row="2" Margin="0,0,0,16">
<Grid.ColumnDefinitions>
<!-- Left: DB Users Grid -->
<ColumnDefinition Width="5*" />
<!-- Center: Action Buttons -->
<ColumnDefinition Width="130" />
<!-- Right: AD Group Members Grid -->
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<!-- LEFT: DB Users Grid -->
<Border Grid.Column="0" Style="{StaticResource ModernCardStyle}" Margin="0" Padding="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Title & Batch Selector -->
<DockPanel Grid.Row="0" Margin="0,0,0,10">
<TextBlock Text="DB 조회 사용자 목록 (권한 할당 대상)" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="BtnSelectAll" Content="전체선택" Style="{StaticResource SecondaryButtonStyle}" Padding="8,3" FontSize="11" Margin="0,0,6,0" />
<Button x:Name="BtnDeselectAll" Content="선택해제" Style="{StaticResource SecondaryButtonStyle}" Padding="8,3" FontSize="11" />
</StackPanel>
</DockPanel>
<!-- DB Users DataGrid with CheckBox -->
<DataGrid x:Name="DbUsersGrid" Grid.Row="1" Style="{StaticResource ModernDataGridStyle}">
<DataGrid.Columns>
<!-- Selection Checkbox Column -->
<DataGridCheckBoxColumn Header="선택" Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" Width="50" />
<DataGridTextColumn Header="아이디 (sAMAccountName)" Binding="{Binding SamAccountName}" Width="150" />
<DataGridTextColumn Header="성명" Binding="{Binding UserName}" Width="100" />
<DataGridTextColumn Header="부서" Binding="{Binding Department}" Width="120" />
<DataGridTextColumn Header="권한 상태" Binding="{Binding Status}" Width="110" />
<DataGridTextColumn Header="할당 일자" Binding="{Binding AssignedDate}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="2" x:Name="TxtDbCount" Text="총 5명 조회됨" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}" Margin="4,8,0,0" />
</Grid>
</Border>
<!-- CENTER: Transfer & Action Buttons -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10,0">
<!-- Add Button -->
<Button x:Name="BtnAddToShareGroup" Style="{StaticResource PrimaryButtonStyle}" Margin="0,0,0,12" Padding="8,12" ToolTip="선택한 사용자를 DB 등록 및 AD 보안그룹에 추가">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="&#xE72A;" FontFamily="{StaticResource IconFont}" FontSize="18" HorizontalAlignment="Center" />
<TextBlock Text="그룹 추가" FontSize="11" FontWeight="Bold" Margin="0,4,0,0" HorizontalAlignment="Center" />
</StackPanel>
</Button>
<!-- Remove / Revoke Button -->
<Button x:Name="BtnRemoveFromShareGroup" Style="{StaticResource SecondaryButtonStyle}" Padding="8,12" ToolTip="선택한 사용자를 DB 해제 및 AD 보안그룹에서 제거">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="&#xE72B;" FontFamily="{StaticResource IconFont}" FontSize="18" HorizontalAlignment="Center" />
<TextBlock Text="그룹 해제" FontSize="11" FontWeight="Bold" Margin="0,4,0,0" HorizontalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
<!-- RIGHT: AD Security Group Members Grid -->
<Border Grid.Column="2" Style="{StaticResource ModernCardStyle}" Margin="0" Padding="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="0,0,0,10">
<TextBlock Text="현재 AD 보안그룹 소속 멤버" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center" />
<Border Background="#10B981" CornerRadius="4" Padding="6,2" HorizontalAlignment="Right">
<TextBlock Text="실시간 AD 동기화" FontSize="11" FontWeight="Bold" Foreground="White" />
</Border>
</DockPanel>
<DataGrid x:Name="ADGroupMembersGrid" Grid.Row="1" Style="{StaticResource ModernDataGridStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="sAMAccountName" Binding="{Binding SamAccountName}" Width="150" />
<DataGridTextColumn Header="성명" Binding="{Binding DisplayName}" Width="100" />
<DataGridTextColumn Header="부서" Binding="{Binding Department}" Width="130" />
<DataGridTextColumn Header="DistinguishedName (DN)" Binding="{Binding DistinguishedName}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="2" x:Name="TxtAdGroupCount" Text="현재 그룹 멤버: 3명" FontSize="11" Foreground="{StaticResource TextSecondaryBrush}" Margin="4,8,0,0" />
</Grid>
</Border>
</Grid>
<!-- 3. Bottom Execution Log & Trace Console -->
<Border Grid.Row="3" Style="{StaticResource ModernCardStyle}" Margin="0" Padding="12">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="0,0,0,6">
<TextBlock Text="동기화 실행 로그 및 SQL/LDAP 처리 이력" FontSize="13" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}" />
<Button x:Name="BtnClearLog" Content="로그 비우기" Style="{StaticResource SecondaryButtonStyle}" Padding="8,2" FontSize="11" HorizontalAlignment="Right" />
</DockPanel>
<Border Grid.Row="1" Background="#090D16" CornerRadius="4" Padding="10">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox x:Name="TxtSyncLogs" Background="Transparent" Foreground="#A78BFA" BorderThickness="0"
IsReadOnly="True" FontFamily="Consolas" FontSize="12" TextWrapping="Wrap"
Text="[시스템 준비 완료] 대상을 선택하고 [그룹 추가] 또는 [그룹 해제]를 실행하십시오." />
</ScrollViewer>
</Border>
</Grid>
</Border>
</Grid>
</UserControl>