Jun 22, 2026, 1:43 PM

This commit is contained in:
Paweł Domański
2026-06-22 11:43:16 +00:00
parent bd0589737e
commit c29b30d2b1
+221 -5
View File
@@ -13,23 +13,34 @@ if (Test-Path $configPath) {
$settings = [pscustomobject]@{ LastServer = ""; Theme = "Light"; Servers = @("localhost") } $settings = [pscustomobject]@{ LastServer = ""; Theme = "Light"; Servers = @("localhost") }
} }
# Ensure Servers is a collection and initialized # Ensure Servers & SplunkHosts are collection and initialized
if ($null -eq $settings.Servers) { if ($null -eq $settings.Servers -or $null -eq $settings.SplunkHosts) {
$servers = if ($null -ne $settings.Servers) { $settings.Servers } else { @("localhost") }
$splunkHosts = if ($null -ne $settings.SplunkHosts) { $settings.SplunkHosts } else { @("sf-host-prod-01", "sf-host-prod-02", "sf-host-staging-01") }
if ($settings -is [System.Management.Automation.PSCustomObject]) { if ($settings -is [System.Management.Automation.PSCustomObject]) {
$props = [ordered]@{ $props = [ordered]@{
LastServer = $settings.LastServer LastServer = $settings.LastServer
Theme = $settings.Theme Theme = $settings.Theme
Servers = @("localhost") Servers = $servers
SplunkHosts = $splunkHosts
SplunkRealm = if ($null -ne $settings.SplunkRealm) { $settings.SplunkRealm } else { "eu0" }
SplunkToken = if ($null -ne $settings.SplunkToken) { $settings.SplunkToken } else { "YOUR_TOKEN" }
} }
$settings = [pscustomobject]$props $settings = [pscustomobject]$props
} else { } else {
$settings = [pscustomobject]@{ LastServer = ""; Theme = "Light"; Servers = @("localhost") } $settings = [pscustomobject]@{
LastServer = ""; Theme = "Light"; Servers = $servers; SplunkHosts = $splunkHosts; SplunkRealm = "eu0"; SplunkToken = "YOUR_TOKEN"
}
} }
} }
if ($settings.Servers -is [string]) { if ($settings.Servers -is [string]) {
$settings.Servers = @($settings.Servers) $settings.Servers = @($settings.Servers)
} }
if ($settings.SplunkHosts -is [string]) {
$settings.SplunkHosts = @($settings.SplunkHosts)
}
# 1. Path to XAML # 1. Path to XAML
$xamlFile = Join-Path $PSScriptRoot "UI\MainWindow.xaml" $xamlFile = Join-Path $PSScriptRoot "UI\MainWindow.xaml"
@@ -50,7 +61,13 @@ $tvServers = $Window.FindName("tvServers")
$btnAddServer = $Window.FindName("btnAddServer") $btnAddServer = $Window.FindName("btnAddServer")
$btnRemoveServer = $Window.FindName("btnRemoveServer") $btnRemoveServer = $Window.FindName("btnRemoveServer")
$gridDetails = $Window.FindName("gridDetails") $gridDetails = $Window.FindName("gridDetails")
$menuHostSlunk = $Window.FindName("menuHostSlunk") $menuSqlServers = $Window.FindName("menuSqlServers")
$menuHostSplunk = $Window.FindName("menuHostSplunk")
$grpDirectory = $Window.FindName("grpDirectory")
# Current view state: "SQL" or "Splunk"
$script:CurrentView = "SQL"
$script:SplunkHosts = @()
# Initialize connection states cache per server # Initialize connection states cache per server
$script:ServerConnectionStates = @{} $script:ServerConnectionStates = @{}
@@ -68,11 +85,144 @@ function Write-Log {
}) })
} }
# 4.0. Get Splunk API Hosts function
function Get-SplunkAPIHosts {
$realm = $settings.SplunkRealm
if ([string]::IsNullOrWhiteSpace($realm)) { $realm = "eu0" }
$token = $env:SF_TOKEN
if ([string]::IsNullOrWhiteSpace($token)) {
$token = $settings.SplunkToken
}
# Bezpieczne wymuszenie protokołu TLS 1.2 dla starszych wersji PowerShell
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
# Jeśli brak tokenu lub jest domyślny, spróbujemy poprosić użytkownika o podanie, lub użyjemy demo
if ([string]::IsNullOrWhiteSpace($token) -or $token -eq "YOUR_TOKEN") {
Write-Log "Brak zdefiniowanego tokenu API Splunk Observability. Uruchamiam wersję demo (lub wpisz token w InputBox)."
try {
Add-Type -AssemblyName Microsoft.VisualBasic
$inputToken = [Microsoft.VisualBasic.Interaction]::InputBox("Wprowadź swój token API Splunk Observability (lub pozostaw puste, aby wyświetlić hosty demonstracyjne):", "Wymagana Autoryzacja Splunk", "")
if (-not [string]::IsNullOrWhiteSpace($inputToken)) {
$token = $inputToken.Trim()
$settings.SplunkToken = $token
$settings | ConvertTo-Json | Set-Content $configPath
Write-Log "Zapisano podany token w settings.json."
}
} catch {
# Ignoruj błędy ładowania VisualBasic w niektórych konfiguracjach
}
}
if ([string]::IsNullOrWhiteSpace($token) -or $token -eq "YOUR_TOKEN") {
Write-Log "Używam demonstracyjnej (symulowanej) listy hostów Splunk Observability Cloud."
return @("splunk-host-eu-prod-01", "splunk-host-eu-prod-02", "splunk-host-eu-prod-03", "splunk-host-eu-staging-01", "splunk-host-eu-dev-01")
}
$headers = @{
"X-SF-TOKEN" = $token
"Content-Type" = "application/json"
}
$hostsSet = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
# 1. Metoda główna: GET /v2/dimension?query=host
try {
Write-Log "Pobieranie wymiarów hosta z API Splunk (Realm: $realm)..."
$limit = 100
$offset = 0
$hasMore = $true
while ($hasMore) {
$uri = "https://api.$realm.observability.splunkcloud.com/v2/dimension?query=host&limit=$limit&offset=$offset"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers -TimeoutSec 10 -ErrorAction Stop
$results = $null
if ($null -ne $response) {
if ($null -ne $response.results) { $results = $response.results }
elseif ($response -is [System.Array]) { $results = $response }
}
if (($null -eq $results -or $results.Count -eq 0) -and $offset -eq 0) {
# Alternatywne query=key:host
$uri = "https://api.$realm.observability.splunkcloud.com/v2/dimension?query=key:host&limit=$limit&offset=$offset"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers -TimeoutSec 10 -ErrorAction Stop
if ($null -ne $response -and $null -ne $response.results) {
$results = $response.results
}
}
if ($null -ne $results -and $results.Count -gt 0) {
foreach ($item in $results) {
if ($item.key -eq "host" -and -not [string]::IsNullOrWhiteSpace($item.value)) {
[void]$hostsSet.Add($item.value.Trim())
}
}
if ($results.Count -lt $limit) { $hasMore = $false } else { $offset += $limit }
} else {
$hasMore = $false
}
}
}
catch {
Write-Log "Metoda 1 (dimension) nie powiodła się: $($_.Exception.Message). Próba Metody 2 (MTS)..."
}
# 2. Metoda Fallback: GET /v2/metrictimeseries?query=host:*
if ($hostsSet.Count -eq 0) {
try {
Write-Log "Uruchamiam fallback: Pobieranie MTS z API Splunk..."
$limit = 100
$offset = 0
$hasMore = $true
while ($hasMore) {
$uri = "https://api.$realm.observability.splunkcloud.com/v2/metrictimeseries?query=host:*&limit=$limit&offset=$offset"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers -TimeoutSec 10 -ErrorAction Stop
$results = $null
if ($null -ne $response) {
if ($null -ne $response.results) { $results = $response.results }
elseif ($response -is [System.Array]) { $results = $response }
}
if ($null -ne $results -and $results.Count -gt 0) {
foreach ($mts in $results) {
if ($null -ne $mts.dimensions -and $null -ne $mts.dimensions.host) {
$val = $mts.dimensions.host
if (-not [string]::IsNullOrWhiteSpace($val)) {
[void]$hostsSet.Add($val.Trim())
}
}
}
if ($results.Count -lt $limit) { $hasMore = $false } else { $offset += $limit }
} else {
$hasMore = $false
}
}
}
catch {
Write-Log "Metoda 2 (fallback) nie powiodła się: $($_.Exception.Message)"
}
}
if ($hostsSet.Count -gt 0) {
$sorted = $hostsSet | Sort-Object
Write-Log "Pomyślnie pobrano $($sorted.Count) hostów ze Splunk Observability Cloud."
return $sorted
} else {
Write-Log "Brak hostów z API. Używam demonstracyjnej (symulowanej) listy hostów."
return @("splunk-host-eu-prod-01", "splunk-host-eu-prod-02", "splunk-host-eu-prod-03", "splunk-host-eu-staging-01", "splunk-host-eu-dev-01")
}
}
# 4.1. TreeView population and refresh # 4.1. TreeView population and refresh
function Refresh-ServerTree { function Refresh-ServerTree {
$tvServers.Dispatcher.Invoke({ $tvServers.Dispatcher.Invoke({
$tvServers.Items.Clear() $tvServers.Items.Clear()
if ($script:CurrentView -eq "SQL") {
$rootNode = New-Object System.Windows.Controls.TreeViewItem $rootNode = New-Object System.Windows.Controls.TreeViewItem
$rootNode.Header = "SQL Servers" $rootNode.Header = "SQL Servers"
$rootNode.IsExpanded = $true $rootNode.IsExpanded = $true
@@ -150,6 +300,72 @@ function Refresh-ServerTree {
} }
$tvServers.Items.Add($rootNode) $tvServers.Items.Add($rootNode)
} else {
# Splunk Hosts View
$rootNode = New-Object System.Windows.Controls.TreeViewItem
$rootNode.Header = "Splunk Observability Hosts"
$rootNode.IsExpanded = $true
$rootNode.Tag = "__ROOT__"
foreach ($hostName in $script:SplunkHosts) {
$hostNode = New-Object System.Windows.Controls.TreeViewItem
$hostNode.Header = $hostName
$hostNode.Tag = $hostName
# Add a dummy loading node so the expansion arrow is displayed
$dummyNode = New-Object System.Windows.Controls.TreeViewItem
$dummyNode.Header = "Loading..."
$hostNode.Items.Add($dummyNode)
# Event handler when a Splunk host node is expanded
$hostNode.Add_Expanded({
param($sender, $e)
if ($e.OriginalSource -ne $sender) { return }
$node = $sender
if ($node.Items.Count -eq 1 -and $node.Items[0].Header -eq "Loading...") {
$node.Items.Clear()
$hName = $node.Tag
Write-Log "Fetching active dimensions/properties for Splunk host $hName..."
# Generate mock indices / indexes for Splunk host representation
$indexes = @(
[pscustomobject]@{ Name = "main_index"; Status = "Active"; Size = "1.2 GB"; RecoveryModel = "N/A"; LastFullBackup = "N/A"; LastLogBackup = "N/A" }
[pscustomobject]@{ Name = "infrastructure_telemetry"; Status = "Active"; Size = "15.4 GB"; RecoveryModel = "N/A"; LastFullBackup = "N/A"; LastLogBackup = "N/A" }
[pscustomobject]@{ Name = "audit_logs"; Status = "Active"; Size = "0.8 GB"; RecoveryModel = "N/A"; LastFullBackup = "N/A"; LastLogBackup = "N/A" }
)
# Cache Splunk host info
$script:ServerConnectionStates[$hName] = @{
Success = $true
Instance = $hName
Version = "Splunk Agent v5.4.1"
Edition = "Enterprise Observability"
Status = "Active / Online"
Databases = $indexes
Errors = @(
[pscustomobject]@{ LogDate = (Get-Date).AddMinutes(-5).ToString(); Message = "High memory usage alert on host $hName"; Source = "SplunkObservability" }
[pscustomobject]@{ LogDate = (Get-Date).AddMinutes(-20).ToString(); Message = "Metric forwarder queue processed successfully"; Source = "Forwarder" }
)
}
foreach ($idx in $indexes) {
$idxNode = New-Object System.Windows.Controls.TreeViewItem
$idxNode.Header = "$($idx.Name) ($($idx.Status))"
$idxNode.Tag = $idx
$node.Items.Add($idxNode)
}
Write-Log "Loaded active indexes for $hName."
}
})
$rootNode.Items.Add($hostNode)
}
$tvServers.Items.Add($rootNode)
}
}) })
} }