main acc3dc0a2689 cached
900 files
45.7 MB
4.2M tokens
12936 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (16,718K chars total). Download the full file to get everything.
Repository: Ozark-Connect/NetworkOptimizer
Branch: main
Commit: acc3dc0a2689
Files: 900
Total size: 45.7 MB

Directory structure:
gitextract_r0774cwm/

├── .dockerignore
├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── Directory.Build.props
├── LICENSE
├── NetworkOptimizer.sln
├── README.md
├── TODO.md
├── docker/
│   ├── .dockerignore
│   ├── .env.example
│   ├── DEPLOYMENT.md
│   ├── Dockerfile
│   ├── NATIVE-DEPLOYMENT.md
│   ├── QUICK-REFERENCE.md
│   ├── README.md
│   ├── docker-compose.local.yml
│   ├── docker-compose.macos.yml
│   ├── docker-compose.prod.yml
│   ├── docker-compose.yml
│   ├── entrypoint.sh
│   ├── grafana/
│   │   ├── dashboards/
│   │   │   ├── network-overview.json
│   │   │   ├── security-posture.json
│   │   │   ├── sqm-performance.json
│   │   │   └── switch-deep-dive.json
│   │   └── provisioning/
│   │       ├── dashboards/
│   │       │   └── dashboards.yml
│   │       └── datasources/
│   │           └── influxdb.yml
│   └── openspeedtest/
│       ├── Dockerfile
│       ├── entrypoint.sh
│       └── nginx.conf
├── docs/
│   ├── MACOS-INSTALLATION.md
│   ├── PLAN-unifi-api-abstraction.md
│   └── features/
│       └── speed-test-roadmap.md
├── nuget.config
├── packages/
│   ├── Blazor-ApexCharts.6.1.1-ozarkconnect.1.nupkg
│   └── Blazor-ApexCharts.6.1.1-ozarkconnect.2.nupkg
├── renovate.json
├── scripts/
│   ├── README.md
│   ├── build-installer.ps1
│   ├── build.sh
│   ├── clean.sh
│   ├── coverage.runsettings
│   ├── coverage.sh
│   ├── deploy-external-speedtest.sh
│   ├── docker-build.sh
│   ├── docker-run.sh
│   ├── docker-stop.sh
│   ├── extract-elevation0-from-images.py
│   ├── install-macos-native.sh
│   ├── parse-antenna-patterns.ps1
│   ├── proxmox/
│   │   ├── README.md
│   │   └── install.sh
│   ├── publish.sh
│   ├── reset-password.ps1
│   ├── reset-password.sh
│   ├── sync-perf-tweaks.ps1
│   ├── test.sh
│   └── watch.sh
├── src/
│   ├── NetworkOptimizer.Agents/
│   │   ├── .gitignore
│   │   ├── AgentDeployer.cs
│   │   ├── AgentHealthMonitor.cs
│   │   ├── Models/
│   │   │   ├── AgentConfiguration.cs
│   │   │   ├── DeploymentResult.cs
│   │   │   └── SshCredentials.cs
│   │   ├── NetworkOptimizer.Agents.csproj
│   │   ├── README.md
│   │   ├── ScriptRenderer.cs
│   │   └── Templates/
│   │       ├── install-linux.sh.template
│   │       ├── linux-agent.service.template
│   │       └── linux-agent.sh.template
│   ├── NetworkOptimizer.Alerts/
│   │   ├── AlertCooldownTracker.cs
│   │   ├── AlertCorrelationService.cs
│   │   ├── AlertProcessingService.cs
│   │   ├── AlertRuleEvaluator.cs
│   │   ├── DefaultAlertRules.cs
│   │   ├── Delivery/
│   │   │   ├── DiscordChannelConfig.cs
│   │   │   ├── DiscordDeliveryChannel.cs
│   │   │   ├── EmailChannelConfig.cs
│   │   │   ├── EmailDeliveryChannel.cs
│   │   │   ├── IAlertDeliveryChannel.cs
│   │   │   ├── ISecretDecryptor.cs
│   │   │   ├── NtfyChannelConfig.cs
│   │   │   ├── NtfyDeliveryChannel.cs
│   │   │   ├── SlackChannelConfig.cs
│   │   │   ├── SlackDeliveryChannel.cs
│   │   │   ├── TeamsChannelConfig.cs
│   │   │   ├── TeamsDeliveryChannel.cs
│   │   │   ├── TimestampFormatter.cs
│   │   │   ├── WebhookChannelConfig.cs
│   │   │   └── WebhookDeliveryChannel.cs
│   │   ├── DigestService.cs
│   │   ├── Events/
│   │   │   ├── AlertEvent.cs
│   │   │   ├── AlertEventBus.cs
│   │   │   └── IAlertEventBus.cs
│   │   ├── Interfaces/
│   │   │   ├── IAlertRepository.cs
│   │   │   ├── IDigestStateStore.cs
│   │   │   └── IScheduleRepository.cs
│   │   ├── Models/
│   │   │   ├── AlertHistoryEntry.cs
│   │   │   ├── AlertIncident.cs
│   │   │   ├── AlertRule.cs
│   │   │   ├── DeliveryChannel.cs
│   │   │   └── ScheduledTask.cs
│   │   ├── NetworkOptimizer.Alerts.csproj
│   │   ├── ScheduleService.cs
│   │   └── Templates/
│   │       ├── alert-email.html
│   │       └── digest-email.html
│   ├── NetworkOptimizer.Audit/
│   │   ├── Analyzers/
│   │   │   ├── AuditScorer.cs
│   │   │   ├── FirewallGroupHelper.cs
│   │   │   ├── FirewallRuleAnalyzer.cs
│   │   │   ├── FirewallRuleEvaluator.cs
│   │   │   ├── FirewallRuleOverlapDetector.cs
│   │   │   ├── FirewallRuleParser.cs
│   │   │   ├── HttpAppIds.cs
│   │   │   ├── PortSecurityAnalyzer.cs
│   │   │   ├── UpnpSecurityAnalyzer.cs
│   │   │   └── VlanAnalyzer.cs
│   │   ├── CHANGELOG.md
│   │   ├── ConfigAuditEngine.cs
│   │   ├── Constants/
│   │   │   └── DetectionConstants.cs
│   │   ├── DeviceNameHints.cs
│   │   ├── Dns/
│   │   │   ├── DnatDnsAnalyzer.cs
│   │   │   ├── DnsAppIds.cs
│   │   │   ├── DnsSecurityAnalyzer.cs
│   │   │   ├── DnsStampDecoder.cs
│   │   │   ├── DohProviderRegistry.cs
│   │   │   └── ThirdPartyDnsDetector.cs
│   │   ├── IssueTypes.cs
│   │   ├── Models/
│   │   │   ├── AuditIssue.cs
│   │   │   ├── AuditRequest.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── AuditSeverity.cs
│   │   │   ├── DeviceAllowanceSettings.cs
│   │   │   ├── DeviceDetectionResult.cs
│   │   │   ├── FirewallAction.cs
│   │   │   ├── FirewallRule.cs
│   │   │   ├── NetworkInfo.cs
│   │   │   ├── OfflineClientInfo.cs
│   │   │   ├── PortInfo.cs
│   │   │   ├── SwitchInfo.cs
│   │   │   └── WirelessClientInfo.cs
│   │   ├── NetworkOptimizer.Audit.csproj
│   │   ├── README.md
│   │   ├── Rules/
│   │   │   ├── AccessPortVlanRule.cs
│   │   │   ├── CameraVlanRule.cs
│   │   │   ├── FirewallAnyAnyRule.cs
│   │   │   ├── IAuditRule.cs
│   │   │   ├── IWirelessAuditRule.cs
│   │   │   ├── IotVlanRule.cs
│   │   │   ├── MacRestrictionRule.cs
│   │   │   ├── PortIsolationRule.cs
│   │   │   ├── PortNameHelper.cs
│   │   │   ├── UnusedPortRule.cs
│   │   │   ├── VlanPlacementChecker.cs
│   │   │   ├── VlanSubnetMismatchRule.cs
│   │   │   ├── WiredSubnetMismatchRule.cs
│   │   │   ├── WirelessCameraVlanRule.cs
│   │   │   └── WirelessIotVlanRule.cs
│   │   ├── Scoring/
│   │   │   └── ScoreConstants.cs
│   │   └── Services/
│   │       ├── Detectors/
│   │       │   ├── FingerprintDetector.cs
│   │       │   ├── MacOuiDetector.cs
│   │       │   └── NamePatternDetector.cs
│   │       ├── DeviceTypeDetectionService.cs
│   │       ├── FirewallZoneLookup.cs
│   │       ├── IIeeeOuiDatabase.cs
│   │       └── IeeeOuiDatabase.cs
│   ├── NetworkOptimizer.Core/
│   │   ├── Caching/
│   │   │   └── AsyncCachedValue.cs
│   │   ├── Enums/
│   │   │   ├── AgentType.cs
│   │   │   ├── AlertSeverity.cs
│   │   │   ├── AlertStatus.cs
│   │   │   ├── AuditSeverity.cs
│   │   │   ├── ClientDeviceCategory.cs
│   │   │   ├── DeviceType.cs
│   │   │   └── MeasurementType.cs
│   │   ├── Extensions/
│   │   │   └── ServiceProviderExtensions.cs
│   │   ├── FeatureFlags.cs
│   │   ├── Helpers/
│   │   │   ├── CloudflareIpRanges.cs
│   │   │   ├── DisplayFormatters.cs
│   │   │   ├── JsonExtensions.cs
│   │   │   ├── NetworkFormatHelpers.cs
│   │   │   ├── NetworkUtilities.cs
│   │   │   └── ProcessUtilities.cs
│   │   ├── Interfaces/
│   │   │   ├── IAgentDeployer.cs
│   │   │   ├── IAuditEngine.cs
│   │   │   ├── IMetricsStorage.cs
│   │   │   ├── IReportGenerator.cs
│   │   │   ├── ISqmManager.cs
│   │   │   └── IUniFiApiClient.cs
│   │   ├── Models/
│   │   │   ├── AgentStatus.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── NetworkConfiguration.cs
│   │   │   ├── ProtectCamera.cs
│   │   │   ├── SqmConfiguration.cs
│   │   │   └── UniFiDevice.cs
│   │   ├── NetworkOptimizer.Core.csproj
│   │   └── VendorSpecificAttribute.cs
│   ├── NetworkOptimizer.Diagnostics/
│   │   ├── Analyzers/
│   │   │   ├── ApLockAnalyzer.cs
│   │   │   ├── PerformanceAnalyzer.cs
│   │   │   ├── PortProfile8021xAnalyzer.cs
│   │   │   ├── PortProfileSuggestionAnalyzer.cs
│   │   │   ├── StreamingAppIds.cs
│   │   │   └── TrunkConsistencyAnalyzer.cs
│   │   ├── DiagnosticsEngine.cs
│   │   ├── Models/
│   │   │   ├── AccessPortVlanIssue.cs
│   │   │   ├── ApLockIssue.cs
│   │   │   ├── DiagnosticSeverity.cs
│   │   │   ├── DiagnosticsResult.cs
│   │   │   ├── PerformanceIssue.cs
│   │   │   ├── PortProfile8021xIssue.cs
│   │   │   ├── PortProfileSuggestion.cs
│   │   │   └── TrunkConsistencyIssue.cs
│   │   └── NetworkOptimizer.Diagnostics.csproj
│   ├── NetworkOptimizer.Installer/
│   │   ├── CustomStrings.wxl
│   │   ├── Iperf3/
│   │   │   ├── .gitignore
│   │   │   └── Download-Iperf3.ps1
│   │   ├── Iperf3Component.wxs
│   │   ├── LICENSE.txt
│   │   ├── License.rtf
│   │   ├── NetworkOptimizer.Installer.wixproj
│   │   ├── Package.wxs
│   │   ├── ServiceComponent.wxs
│   │   ├── SpeedTest/
│   │   │   ├── .gitignore
│   │   │   ├── Download-Nginx.ps1
│   │   │   ├── Start-SpeedTest.ps1
│   │   │   ├── config.js.template
│   │   │   └── nginx.conf
│   │   ├── SpeedTestComponent.wxs
│   │   ├── Traefik/
│   │   │   ├── .gitignore
│   │   │   └── Download-Traefik.ps1
│   │   └── TraefikComponent.wxs
│   ├── NetworkOptimizer.Monitoring/
│   │   ├── AlertEngine.cs
│   │   ├── MetricsAggregator.cs
│   │   ├── Models/
│   │   │   ├── Alert.cs
│   │   │   ├── AlertThreshold.cs
│   │   │   ├── CellularModemStats.cs
│   │   │   ├── DeviceMetrics.cs
│   │   │   └── InterfaceMetrics.cs
│   │   ├── NetworkOptimizer.Monitoring.csproj
│   │   ├── QmicliParser.cs
│   │   ├── README.md
│   │   ├── SnmpConfiguration.cs
│   │   ├── SnmpPoller.cs
│   │   └── UniFiOids.cs
│   ├── NetworkOptimizer.Reports/
│   │   ├── BrandingOptions.cs
│   │   ├── INDEX.md
│   │   ├── MarkdownReportGenerator.cs
│   │   ├── NetworkOptimizer.Reports.csproj
│   │   ├── PdfReportGenerator.cs
│   │   ├── README.md
│   │   ├── ReportData.cs
│   │   └── Templates/
│   │       └── .gitkeep
│   ├── NetworkOptimizer.Sqm/
│   │   ├── ARCHITECTURE.md
│   │   ├── BaselineCalculator.cs
│   │   ├── InputSanitizer.cs
│   │   ├── LatencyMonitor.cs
│   │   ├── Models/
│   │   │   ├── BaselineData.cs
│   │   │   ├── ConnectionProfile.cs
│   │   │   ├── SpeedtestResult.cs
│   │   │   ├── SqmConfiguration.cs
│   │   │   └── SqmStatus.cs
│   │   ├── NetworkOptimizer.Sqm.csproj
│   │   ├── README.md
│   │   ├── ScriptGenerator.cs
│   │   ├── SpeedtestIntegration.cs
│   │   └── SqmManager.cs
│   ├── NetworkOptimizer.Storage/
│   │   ├── .gitignore
│   │   ├── Helpers/
│   │   │   └── SpeedTestFilterHelper.cs
│   │   ├── InfluxDbStorage.cs
│   │   ├── Interfaces/
│   │   │   ├── IAgentRepository.cs
│   │   │   ├── IAuditRepository.cs
│   │   │   ├── IMetricsStorage.cs
│   │   │   ├── IModemRepository.cs
│   │   │   ├── ISettingsRepository.cs
│   │   │   ├── ISpeedTestRepository.cs
│   │   │   ├── ISqmRepository.cs
│   │   │   └── IUniFiRepository.cs
│   │   ├── Migrations/
│   │   │   ├── 20251208000000_InitialCreate.Designer.cs
│   │   │   ├── 20251208000000_InitialCreate.cs
│   │   │   ├── 20251210000000_AddModemAndSpeedTables.Designer.cs
│   │   │   ├── 20251210000000_AddModemAndSpeedTables.cs
│   │   │   ├── 20251216000000_AddUniFiSshSettings.Designer.cs
│   │   │   ├── 20251216000000_AddUniFiSshSettings.cs
│   │   │   ├── 20251217000000_AddDismissedIssues.Designer.cs
│   │   │   ├── 20251217000000_AddDismissedIssues.cs
│   │   │   ├── 20251217100000_AddGatewaySshSettings.Designer.cs
│   │   │   ├── 20251217100000_AddGatewaySshSettings.cs
│   │   │   ├── 20251217200000_AddStartIperf3ServerToDeviceConfig.Designer.cs
│   │   │   ├── 20251217200000_AddStartIperf3ServerToDeviceConfig.cs
│   │   │   ├── 20251217300000_AddSystemSettings.Designer.cs
│   │   │   ├── 20251217300000_AddSystemSettings.cs
│   │   │   ├── 20251218000000_AddSshCredentialOverridesToDeviceConfig.Designer.cs
│   │   │   ├── 20251218000000_AddSshCredentialOverridesToDeviceConfig.cs
│   │   │   ├── 20251219000000_AddUniFiConnectionSettings.Designer.cs
│   │   │   ├── 20251219000000_AddUniFiConnectionSettings.cs
│   │   │   ├── 20251224000000_AddPathAnalysisJson.Designer.cs
│   │   │   ├── 20251224000000_AddPathAnalysisJson.cs
│   │   │   ├── 20251227000000_AddTcMonitorPort.Designer.cs
│   │   │   ├── 20251227000000_AddTcMonitorPort.cs
│   │   │   ├── 20251227100000_AddSqmWanConfiguration.Designer.cs
│   │   │   ├── 20251227100000_AddSqmWanConfiguration.cs
│   │   │   ├── 20251228000000_AddAdminSettings.Designer.cs
│   │   │   ├── 20251228000000_AddAdminSettings.cs
│   │   │   ├── 20251228100000_AddSqmSpeedtestSchedule.Designer.cs
│   │   │   ├── 20251228100000_AddSqmSpeedtestSchedule.cs
│   │   │   ├── 20251229000000_AddReportDataJson.Designer.cs
│   │   │   ├── 20251229000000_AddReportDataJson.cs
│   │   │   ├── 20260102000000_AddLocalIpToIperf3Result.Designer.cs
│   │   │   ├── 20260102000000_AddLocalIpToIperf3Result.cs
│   │   │   ├── 20260103000000_AddIgnoreControllerSSLErrors.Designer.cs
│   │   │   ├── 20260103000000_AddIgnoreControllerSSLErrors.cs
│   │   │   ├── 20260104100000_AddClientSpeedTestFieldsToIperf3Result.Designer.cs
│   │   │   ├── 20260104100000_AddClientSpeedTestFieldsToIperf3Result.cs
│   │   │   ├── 20260106000000_AddLocationAndWifiSignal.Designer.cs
│   │   │   ├── 20260106000000_AddLocationAndWifiSignal.cs
│   │   │   ├── 20260107000000_AddWifiRadio.Designer.cs
│   │   │   ├── 20260107000000_AddWifiRadio.cs
│   │   │   ├── 20260107100000_AddWifiMlo.Designer.cs
│   │   │   ├── 20260107100000_AddWifiMlo.cs
│   │   │   ├── 20260107200000_AddWifiTxRxRates.Designer.cs
│   │   │   ├── 20260107200000_AddWifiTxRxRates.cs
│   │   │   ├── 20260110000000_AddIperf3BinaryPathToDeviceConfig.Designer.cs
│   │   │   ├── 20260110000000_AddIperf3BinaryPathToDeviceConfig.cs
│   │   │   ├── 20260113000000_AddUpnpNotes.Designer.cs
│   │   │   ├── 20260113000000_AddUpnpNotes.cs
│   │   │   ├── 20260124000000_AddNotesToIperf3Result.Designer.cs
│   │   │   ├── 20260124000000_AddNotesToIperf3Result.cs
│   │   │   ├── 20260209200000_AddApLocations.Designer.cs
│   │   │   ├── 20260209200000_AddApLocations.cs
│   │   │   ├── 20260210100000_AddLoadedLatencyColumns.Designer.cs
│   │   │   ├── 20260210100000_AddLoadedLatencyColumns.cs
│   │   │   ├── 20260211000000_AddWanIdentityColumns.Designer.cs
│   │   │   ├── 20260211000000_AddWanIdentityColumns.cs
│   │   │   ├── 20260211200000_AddBuildingsAndFloorPlans.Designer.cs
│   │   │   ├── 20260211200000_AddBuildingsAndFloorPlans.cs
│   │   │   ├── 20260211300000_AddApOrientationDeg.Designer.cs
│   │   │   ├── 20260211300000_AddApOrientationDeg.cs
│   │   │   ├── 20260211400000_AddApMountType.Designer.cs
│   │   │   ├── 20260211400000_AddApMountType.cs
│   │   │   ├── 20260212000000_AddFloorMaterial.Designer.cs
│   │   │   ├── 20260212000000_AddFloorMaterial.cs
│   │   │   ├── 20260213000000_AddClientSignalLog.Designer.cs
│   │   │   ├── 20260213000000_AddClientSignalLog.cs
│   │   │   ├── 20260213000000_AddPlannedAps.Designer.cs
│   │   │   ├── 20260213000000_AddPlannedAps.cs
│   │   │   ├── 20260214100000_AddPerBandTxPower.Designer.cs
│   │   │   ├── 20260214100000_AddPerBandTxPower.cs
│   │   │   ├── 20260220000000_AddFloorPlanImages.Designer.cs
│   │   │   ├── 20260220000000_AddFloorPlanImages.cs
│   │   │   ├── 20260221000000_AddAlertTables.Designer.cs
│   │   │   ├── 20260221000000_AddAlertTables.cs
│   │   │   ├── 20260221100000_AddThreatTables.Designer.cs
│   │   │   ├── 20260221100000_AddThreatTables.cs
│   │   │   ├── 20260222100000_AddTrafficFlowFields.Designer.cs
│   │   │   ├── 20260222100000_AddTrafficFlowFields.cs
│   │   │   ├── 20260222200000_AddThreatNoiseFilters.Designer.cs
│   │   │   ├── 20260222200000_AddThreatNoiseFilters.cs
│   │   │   ├── 20260223000000_AddScheduledTasks.Designer.cs
│   │   │   ├── 20260223000000_AddScheduledTasks.cs
│   │   │   ├── 20260223100000_AddAlertRuleThreshold.Designer.cs
│   │   │   ├── 20260223100000_AddAlertRuleThreshold.cs
│   │   │   ├── 20260225200000_AddPatternLastAlertedAt.Designer.cs
│   │   │   ├── 20260225200000_AddPatternLastAlertedAt.cs
│   │   │   ├── 20260226010000_AddPatternDedupKey.Designer.cs
│   │   │   ├── 20260226010000_AddPatternDedupKey.cs
│   │   │   ├── 20260226100000_AddAuditIsScheduled.Designer.cs
│   │   │   ├── 20260226100000_AddAuditIsScheduled.cs
│   │   │   ├── 20260226120000_AddSqmBaselineLatency.Designer.cs
│   │   │   ├── 20260226120000_AddSqmBaselineLatency.cs
│   │   │   ├── 20260228000000_AddWanDataUsageTables.Designer.cs
│   │   │   ├── 20260228000000_AddWanDataUsageTables.cs
│   │   │   ├── 20260301000000_AddSignalLogChannelWidth.Designer.cs
│   │   │   ├── 20260301000000_AddSignalLogChannelWidth.cs
│   │   │   ├── 20260301200000_AddSnapshotGatewayBootTime.Designer.cs
│   │   │   ├── 20260301200000_AddSnapshotGatewayBootTime.cs
│   │   │   ├── 20260306000000_AddAlertSourceUrl.Designer.cs
│   │   │   ├── 20260306000000_AddAlertSourceUrl.cs
│   │   │   ├── 20260311000000_AddDeviceIperf3Overrides.Designer.cs
│   │   │   ├── 20260311000000_AddDeviceIperf3Overrides.cs
│   │   │   ├── 20260312000000_PurgeStaleCrowdSecNegativeCache.Designer.cs
│   │   │   ├── 20260312000000_PurgeStaleCrowdSecNegativeCache.cs
│   │   │   ├── 20260318000000_AddExternalServerName.Designer.cs
│   │   │   ├── 20260318000000_AddExternalServerName.cs
│   │   │   ├── 20260320000000_AddWanSteerTrafficClasses.Designer.cs
│   │   │   ├── 20260320000000_AddWanSteerTrafficClasses.cs
│   │   │   ├── 20260402100000_AddCongestionSeverity.Designer.cs
│   │   │   ├── 20260402100000_AddCongestionSeverity.cs
│   │   │   ├── 20260404000000_AddApiKey.Designer.cs
│   │   │   ├── 20260404000000_AddApiKey.cs
│   │   │   ├── 20260405000000_AddExternalSpeedTestServers.Designer.cs
│   │   │   ├── 20260405000000_AddExternalSpeedTestServers.cs
│   │   │   ├── 20260428000000_AddSqmLinkSpeedOverride.Designer.cs
│   │   │   ├── 20260428000000_AddSqmLinkSpeedOverride.cs
│   │   │   ├── 20260505000000_AddSqmBootDelay.Designer.cs
│   │   │   ├── 20260505000000_AddSqmBootDelay.cs
│   │   │   ├── 20260507000000_AddPerfTweakSettings.Designer.cs
│   │   │   ├── 20260507000000_AddPerfTweakSettings.cs
│   │   │   └── NetworkOptimizerDbContextModelSnapshot.cs
│   │   ├── Models/
│   │   │   ├── AdminSettings.cs
│   │   │   ├── AgentConfiguration.cs
│   │   │   ├── ApLocation.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── Building.cs
│   │   │   ├── ClientSignalLog.cs
│   │   │   ├── DeviceSshConfiguration.cs
│   │   │   ├── DismissedIssue.cs
│   │   │   ├── ExternalSpeedTestServer.cs
│   │   │   ├── FloorPlan.cs
│   │   │   ├── FloorPlanImage.cs
│   │   │   ├── GatewaySshSettings.cs
│   │   │   ├── Iperf3Result.cs
│   │   │   ├── LicenseInfo.cs
│   │   │   ├── ModemConfiguration.cs
│   │   │   ├── NetworkOptimizerDbContext.cs
│   │   │   ├── PerfTweakSetting.cs
│   │   │   ├── PlannedAp.cs
│   │   │   ├── SqmBaseline.cs
│   │   │   ├── SqmWanConfiguration.cs
│   │   │   ├── SystemSetting.cs
│   │   │   ├── UniFiConnectionSettings.cs
│   │   │   ├── UniFiSshSettings.cs
│   │   │   ├── UpnpNote.cs
│   │   │   ├── WanDataUsageConfig.cs
│   │   │   ├── WanDataUsageSnapshot.cs
│   │   │   └── WanSteerTrafficClass.cs
│   │   ├── NetworkOptimizer.Storage.csproj
│   │   ├── README.md
│   │   ├── Repositories/
│   │   │   ├── AgentRepository.cs
│   │   │   ├── AlertRepository.cs
│   │   │   ├── AuditRepository.cs
│   │   │   ├── ModemRepository.cs
│   │   │   ├── ScheduleRepository.cs
│   │   │   ├── SettingsRepository.cs
│   │   │   ├── SpeedTestRepository.cs
│   │   │   ├── SqmRepository.cs
│   │   │   ├── ThreatRepository.cs
│   │   │   └── UniFiRepository.cs
│   │   ├── RepositoryBase.cs
│   │   ├── Services/
│   │   │   ├── CredentialProtectionService.cs
│   │   │   └── ICredentialProtectionService.cs
│   │   ├── StorageConfiguration.cs
│   │   └── StorageServiceExtensions.cs
│   ├── NetworkOptimizer.Threats/
│   │   ├── Analysis/
│   │   │   ├── BruteForceDetector.cs
│   │   │   ├── DDoSDetector.cs
│   │   │   ├── ExploitCampaignDetector.cs
│   │   │   ├── ExposureValidator.cs
│   │   │   ├── FlowInterestFilter.cs
│   │   │   ├── KillChainClassifier.cs
│   │   │   ├── ScanSweepDetector.cs
│   │   │   └── ThreatPatternAnalyzer.cs
│   │   ├── CrowdSec/
│   │   │   ├── CrowdSecClient.cs
│   │   │   ├── CrowdSecEnrichmentService.cs
│   │   │   └── CrowdSecModels.cs
│   │   ├── Enrichment/
│   │   │   └── GeoEnrichmentService.cs
│   │   ├── Interfaces/
│   │   │   ├── IThreatRepository.cs
│   │   │   ├── IThreatSettingsAccessor.cs
│   │   │   └── IUniFiClientAccessor.cs
│   │   ├── Models/
│   │   │   ├── CrowdSecReputation.cs
│   │   │   ├── EventSource.cs
│   │   │   ├── ExposureReport.cs
│   │   │   ├── GeoInfo.cs
│   │   │   ├── KillChainStage.cs
│   │   │   ├── PatternType.cs
│   │   │   ├── ThreatAction.cs
│   │   │   ├── ThreatEvent.cs
│   │   │   ├── ThreatNoiseFilter.cs
│   │   │   └── ThreatPattern.cs
│   │   ├── NetworkOptimizer.Threats.csproj
│   │   ├── ThreatCollectionService.cs
│   │   └── ThreatEventNormalizer.cs
│   ├── NetworkOptimizer.UniFi/
│   │   ├── ClientIpEnricher.cs
│   │   ├── Helpers/
│   │   │   ├── GlobalSwitchSettings.cs
│   │   │   └── VlanAnalysisHelper.cs
│   │   ├── Models/
│   │   │   ├── NetworkHop.cs
│   │   │   ├── NetworkPath.cs
│   │   │   ├── PathAnalysisResult.cs
│   │   │   ├── UniFiApiResponse.cs
│   │   │   ├── UniFiClientDetailResponse.cs
│   │   │   ├── UniFiClientResponse.cs
│   │   │   ├── UniFiDeviceResponse.cs
│   │   │   ├── UniFiFingerprintDatabase.cs
│   │   │   ├── UniFiFirewallGroup.cs
│   │   │   ├── UniFiFirewallRule.cs
│   │   │   ├── UniFiFirewallZone.cs
│   │   │   ├── UniFiIpsEvent.cs
│   │   │   ├── UniFiNetworkConfig.cs
│   │   │   ├── UniFiPortForwardRule.cs
│   │   │   ├── UniFiPortProfile.cs
│   │   │   ├── UniFiProtectDeviceResponse.cs
│   │   │   ├── UniFiSysInfo.cs
│   │   │   ├── UniFiThreatLogEntry.cs
│   │   │   ├── UniFiWlanConfig.cs
│   │   │   ├── WiFiManClientResponse.cs
│   │   │   └── WirelessRateSnapshot.cs
│   │   ├── NetworkOptimizer.UniFi.csproj
│   │   ├── NetworkPathAnalyzer.cs
│   │   ├── README.md
│   │   ├── RadioFormatHelper.cs
│   │   ├── UniFiApiClient.cs
│   │   ├── UniFiDiscovery.cs
│   │   └── UniFiProductDatabase.cs
│   ├── NetworkOptimizer.Web/
│   │   ├── App.razor
│   │   ├── Components/
│   │   │   ├── Layout/
│   │   │   │   ├── AuthLayout.razor
│   │   │   │   ├── MainLayout.razor
│   │   │   │   └── NavMenu.razor
│   │   │   ├── Pages/
│   │   │   │   ├── Agents.razor
│   │   │   │   ├── Alerts.razor
│   │   │   │   ├── Audit.razor
│   │   │   │   ├── ClientDashboard.razor
│   │   │   │   ├── ClientSpeedTest.razor
│   │   │   │   ├── ClientWanSpeedTest.razor
│   │   │   │   ├── Dashboard.razor
│   │   │   │   ├── Login.razor
│   │   │   │   ├── Optimize.razor
│   │   │   │   ├── PerformanceTweaks.razor
│   │   │   │   ├── PwaInstall.razor
│   │   │   │   ├── Settings.razor
│   │   │   │   ├── SpeedTest.razor
│   │   │   │   ├── Sqm.razor
│   │   │   │   ├── ThreatDashboard.razor
│   │   │   │   ├── UpnpInspector.razor
│   │   │   │   ├── WanSpeedTest.razor
│   │   │   │   ├── WanSteering.razor
│   │   │   │   └── WiFiOptimizer.razor
│   │   │   ├── Routes.razor
│   │   │   ├── ScrollRestoration.razor
│   │   │   ├── Shared/
│   │   │   │   ├── AgentStatusTable.razor
│   │   │   │   ├── AlertsList.razor
│   │   │   │   ├── CellularStatsPanel.razor
│   │   │   │   ├── DeviceCard.razor
│   │   │   │   ├── DeviceIcon.razor
│   │   │   │   ├── IssuesList.razor
│   │   │   │   ├── PwaBanner.razor
│   │   │   │   ├── SecurityScoreGauge.razor
│   │   │   │   ├── SpeedTestDetails.razor
│   │   │   │   ├── SpeedTestMap.razor
│   │   │   │   ├── SpeedTestSearchFilter.razor
│   │   │   │   ├── SponsorshipBanner.razor
│   │   │   │   ├── SqmStatusPanel.razor
│   │   │   │   ├── SshTroubleshootingTooltip.razor
│   │   │   │   ├── UpdateChecker.razor
│   │   │   │   ├── WanOption.cs
│   │   │   │   └── WiFi/
│   │   │   │       ├── AirtimeFairness.razor
│   │   │   │       ├── ApLoadBalance.razor
│   │   │   │       ├── BandSteeringAnalysis.razor
│   │   │   │       ├── ChannelAnalysis.razor
│   │   │   │       ├── ClientTimeline.razor
│   │   │   │       ├── ConnectivityFlow.razor
│   │   │   │       ├── EnvironmentalCorrelation.razor
│   │   │   │       ├── FloorPlanEditor.razor
│   │   │   │       ├── HealthScoreGauge.razor
│   │   │   │       ├── Metrics.razor
│   │   │   │       ├── PowerCoverageAnalysis.razor
│   │   │   │       ├── RoamingAnalytics.razor
│   │   │   │       ├── SpectrumAnalysis.razor
│   │   │   │       └── WiFiDashboardPanel.razor
│   │   │   └── _Imports.razor
│   │   ├── Endpoints/
│   │   │   ├── AlertEndpoints.cs
│   │   │   ├── EndpointHelpers.cs
│   │   │   └── SpeedTestEndpoints.cs
│   │   ├── Models/
│   │   │   ├── ApMapMarker.cs
│   │   │   └── ClientDashboardModels.cs
│   │   ├── NetworkOptimizer.Web.csproj
│   │   ├── Program.cs
│   │   ├── Properties/
│   │   │   └── launchSettings.json
│   │   ├── README.md
│   │   ├── Resources/
│   │   │   └── PerfTweaks/
│   │   │       ├── 06-mongodb-ssd-offload.sh
│   │   │       ├── 07-mongodb-ssd-backup.sh
│   │   │       ├── 10-journald-volatile.sh
│   │   │       ├── 15-fan-control-tuning.sh
│   │   │       ├── 20-sfp-sgmiiplus.sh
│   │   │       └── force_uniphy1_sgmiiplus.ko
│   │   ├── Services/
│   │   │   ├── AdminAuthService.cs
│   │   │   ├── AgentService.cs
│   │   │   ├── ApMapService.cs
│   │   │   ├── AuditService.cs
│   │   │   ├── CellularModemService.cs
│   │   │   ├── ClientDashboardService.cs
│   │   │   ├── ClientSpeedTestService.cs
│   │   │   ├── CloudflareSpeedTestService.cs
│   │   │   ├── ConfigTransferService.cs
│   │   │   ├── DashboardLayoutService.cs
│   │   │   ├── DashboardService.cs
│   │   │   ├── DiagnosticsService.cs
│   │   │   ├── FileVersionProvider.cs
│   │   │   ├── FingerprintDatabaseService.cs
│   │   │   ├── FloorPlanService.cs
│   │   │   ├── GatewaySpeedTestService.cs
│   │   │   ├── GatewayWanSpeedTestService.cs
│   │   │   ├── HeatmapDataCache.cs
│   │   │   ├── IAgentService.cs
│   │   │   ├── ICellularModemService.cs
│   │   │   ├── IDashboardService.cs
│   │   │   ├── IFingerprintDatabaseService.cs
│   │   │   ├── IGatewaySpeedTestService.cs
│   │   │   ├── IIperf3SpeedTestService.cs
│   │   │   ├── ISponsorshipService.cs
│   │   │   ├── ISqmDeploymentService.cs
│   │   │   ├── ISqmService.cs
│   │   │   ├── ISystemSettingsService.cs
│   │   │   ├── ITcMonitorClient.cs
│   │   │   ├── IUniFiSshService.cs
│   │   │   ├── Iperf3JsonParser.cs
│   │   │   ├── Iperf3ServerService.cs
│   │   │   ├── Iperf3SpeedTestService.cs
│   │   │   ├── JwtService.cs
│   │   │   ├── NginxHostedService.cs
│   │   │   ├── PasswordHasher.cs
│   │   │   ├── PdfStorageService.cs
│   │   │   ├── PerfTweaksDeploymentService.cs
│   │   │   ├── PlannedApService.cs
│   │   │   ├── PullToRefreshState.cs
│   │   │   ├── ScheduleExecutorRegistration.cs
│   │   │   ├── SponsorshipService.cs
│   │   │   ├── SqmDeploymentService.cs
│   │   │   ├── SqmService.cs
│   │   │   ├── Ssh/
│   │   │   │   ├── GatewaySshService.cs
│   │   │   │   ├── IGatewaySshService.cs
│   │   │   │   ├── SshClientService.cs
│   │   │   │   ├── SshCommandResult.cs
│   │   │   │   └── SshConnectionInfo.cs
│   │   │   ├── SystemSettingsService.cs
│   │   │   ├── TcMonitorClient.cs
│   │   │   ├── ThreatDashboardService.cs
│   │   │   ├── ThreatSettingsAccessor.cs
│   │   │   ├── TimeFormatHelper.cs
│   │   │   ├── TopologySnapshotService.cs
│   │   │   ├── TraefikHostedService.cs
│   │   │   ├── UniFiClientAccessor.cs
│   │   │   ├── UniFiConnectionService.cs
│   │   │   ├── UniFiSshService.cs
│   │   │   ├── UwnSpeedTestService.cs
│   │   │   ├── WanDataUsageService.cs
│   │   │   ├── WanSpeedTestServiceBase.cs
│   │   │   ├── WanSteerDeploymentService.cs
│   │   │   ├── WanSteerValidation.cs
│   │   │   └── WiFiOptimizerService.cs
│   │   ├── appsettings.Development.json
│   │   ├── appsettings.json
│   │   └── wwwroot/
│   │       ├── css/
│   │       │   └── app.css
│   │       ├── data/
│   │       │   ├── antenna-patterns.json
│   │       │   └── cloudflare-colos.json
│   │       ├── downloads/
│   │       │   ├── iperf3_3.18-1_mips-3.4.ipk
│   │       │   └── libiperf3_3.18-1_mips-3.4.ipk
│   │       ├── js/
│   │       │   ├── demo-mask.js
│   │       │   ├── floorPlanEditor.js
│   │       │   ├── scrollRestoration.js
│   │       │   ├── steppedScaleBar.js
│   │       │   └── updateCheck.js
│   │       ├── lib/
│   │       │   ├── pdf.min.mjs
│   │       │   └── pdf.worker.min.mjs
│   │       └── manifest.webmanifest
│   ├── NetworkOptimizer.WiFi/
│   │   ├── Analyzers/
│   │   │   └── SiteHealthScorer.cs
│   │   ├── BssidIdentifier.cs
│   │   ├── Data/
│   │   │   ├── AntennaPatternLoader.cs
│   │   │   ├── ApModelCatalog.cs
│   │   │   ├── MaterialAttenuation.cs
│   │   │   └── MountTypeHelper.cs
│   │   ├── Helpers/
│   │   │   ├── ChannelSpanHelper.cs
│   │   │   └── SignalClassification.cs
│   │   ├── IWiFiDataProvider.cs
│   │   ├── Models/
│   │   │   ├── AccessPointSnapshot.cs
│   │   │   ├── ChannelRecommendation.cs
│   │   │   ├── ChannelScanResult.cs
│   │   │   ├── ClientConnectionEvent.cs
│   │   │   ├── PropagationModels.cs
│   │   │   ├── RegulatoryChannelData.cs
│   │   │   ├── RoamingEvent.cs
│   │   │   ├── RoamingTopology.cs
│   │   │   ├── WiFiMetrics.cs
│   │   │   ├── WirelessClientSnapshot.cs
│   │   │   └── WlanConfiguration.cs
│   │   ├── NetworkOptimizer.WiFi.csproj
│   │   ├── Providers/
│   │   │   └── UniFiLiveDataProvider.cs
│   │   ├── Rules/
│   │   │   ├── BandSteeringRule.cs
│   │   │   ├── CoChannelInterferenceRule.cs
│   │   │   ├── CoverageGapRule.cs
│   │   │   ├── DhcpIssuesRule.cs
│   │   │   ├── High2GHzConcentrationRule.cs
│   │   │   ├── HighApLoadRule.cs
│   │   │   ├── HighPowerOverlapRule.cs
│   │   │   ├── HighPowerRule.cs
│   │   │   ├── HighRadioUtilizationRule.cs
│   │   │   ├── HighTxRetryRule.cs
│   │   │   ├── IWiFiOptimizerRule.cs
│   │   │   ├── IoTSsidSeparationRule.cs
│   │   │   ├── LegacyClientAirtimeRule.cs
│   │   │   ├── LoadImbalanceRule.cs
│   │   │   ├── MinRssiEnabledRule.cs
│   │   │   ├── MinRssiRule.cs
│   │   │   ├── MinimumDataRatesRule.cs
│   │   │   ├── NonStandardChannelRule.cs
│   │   │   ├── RoamingAssistantRule.cs
│   │   │   ├── TxPowerVariationRule.cs
│   │   │   ├── WeakSignalPopulationRule.cs
│   │   │   ├── WiFiOptimizerContext.cs
│   │   │   ├── WiFiOptimizerEngine.cs
│   │   │   └── WideChannelWidthRule.cs
│   │   ├── Services/
│   │   │   ├── ChannelRecommendationService.cs
│   │   │   └── PropagationService.cs
│   │   ├── SiteHealthScore.cs
│   │   └── WiFiAnalysisHelpers.cs
│   ├── OpenSpeedTest/
│   │   ├── .gitignore
│   │   ├── ATTRIBUTION.md
│   │   ├── License.md
│   │   ├── README.md
│   │   ├── assets/
│   │   │   ├── css/
│   │   │   │   ├── app.css
│   │   │   │   ├── darkmode.css
│   │   │   │   └── ozark-overrides.css
│   │   │   ├── images/
│   │   │   │   └── icons/
│   │   │   │       ├── browserconfig.xml
│   │   │   │       └── site.webmanifest
│   │   │   └── js/
│   │   │       ├── app-2.5.4.js
│   │   │       ├── config.js
│   │   │       ├── darkmode.js
│   │   │       └── geolocation.js
│   │   ├── downloading
│   │   ├── hosted.html
│   │   ├── index.html
│   │   └── upload
│   ├── cfspeedtest/
│   │   ├── .gitignore
│   │   ├── Makefile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── speedtest/
│   │       ├── latency.go
│   │       ├── metadata.go
│   │       ├── servertiming.go
│   │       ├── sockopt_unix.go
│   │       ├── sockopt_windows.go
│   │       ├── throughput.go
│   │       ├── transport.go
│   │       └── types.go
│   ├── uwnspeedtest/
│   │   ├── Makefile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── uwn/
│   │       ├── discovery.go
│   │       ├── latency.go
│   │       ├── throughput.go
│   │       └── types.go
│   └── wansteer/
│       ├── Makefile
│       ├── config.go
│       ├── config.sample.json
│       ├── go.mod
│       ├── health.go
│       ├── main.go
│       ├── rules.go
│       ├── status.go
│       └── wansteer_test.go
└── tests/
    ├── Directory.Build.props
    ├── FluentAssertionsLicense.cs
    ├── NetworkOptimizer.Agents.Tests/
    │   ├── DeploymentResultTests.cs
    │   ├── NetworkOptimizer.Agents.Tests.csproj
    │   └── ScriptRendererTests.cs
    ├── NetworkOptimizer.Alerts.Tests/
    │   ├── AlertCooldownTrackerTests.cs
    │   ├── AlertCorrelationServiceTests.cs
    │   ├── AlertEventBusTests.cs
    │   ├── AlertRuleEvaluatorTests.cs
    │   ├── Delivery/
    │   │   ├── NtfyDeliveryChannelTests.cs
    │   │   └── WebhookDeliveryChannelTests.cs
    │   ├── NetworkOptimizer.Alerts.Tests.csproj
    │   └── ScheduleCalculationTests.cs
    ├── NetworkOptimizer.Audit.Tests/
    │   ├── Analyzers/
    │   │   ├── FirewallGroupHelperTests.cs
    │   │   ├── FirewallRuleAnalyzerTests.cs
    │   │   ├── FirewallRuleEvaluatorTests.cs
    │   │   ├── FirewallRuleOverlapDetectorTests.cs
    │   │   ├── FirewallRuleParserTests.cs
    │   │   ├── HttpAppIdsTests.cs
    │   │   ├── PortProfileResolutionTests.cs
    │   │   ├── PortSecurityAnalyzerTests.cs
    │   │   ├── ProtectCameraFallbackTests.cs
    │   │   ├── UpnpSecurityAnalyzerTests.cs
    │   │   └── VlanAnalyzerTests.cs
    │   ├── AuditScorerTests.cs
    │   ├── ConfigAuditEngineTests.cs
    │   ├── Constants/
    │   │   └── DetectionConstantsTests.cs
    │   ├── DeviceNameHintsTests.cs
    │   ├── Dns/
    │   │   ├── DnatDnsAnalyzerTests.cs
    │   │   ├── DnsAppIdsTests.cs
    │   │   ├── DnsSecurityAnalyzerTests.cs
    │   │   ├── DnsStampDecoderTests.cs
    │   │   ├── DohProviderRegistryTests.cs
    │   │   └── ThirdPartyDnsDetectorTests.cs
    │   ├── Models/
    │   │   ├── AuditRequestTests.cs
    │   │   ├── ClientInfoDisplayNameTests.cs
    │   │   ├── DeviceAllowanceSettingsTests.cs
    │   │   ├── FirewallActionTests.cs
    │   │   ├── FirewallRuleTests.cs
    │   │   └── NetworkPurposeExtensionsTests.cs
    │   ├── NetworkOptimizer.Audit.Tests.csproj
    │   ├── Rules/
    │   │   ├── AccessPortVlanRuleTests.cs
    │   │   ├── AuditRuleBaseTests.cs
    │   │   ├── CameraVlanRuleTests.cs
    │   │   ├── FirewallAnyAnyRuleTests.cs
    │   │   ├── IotVlanRuleTests.cs
    │   │   ├── MacRestrictionRuleTests.cs
    │   │   ├── PortIsolationRuleTests.cs
    │   │   ├── PortNameHelperTests.cs
    │   │   ├── UnusedPortRuleTests.cs
    │   │   ├── VlanPlacementCheckerTests.cs
    │   │   ├── VlanSubnetMismatchRuleTests.cs
    │   │   ├── WiredSubnetMismatchRuleTests.cs
    │   │   ├── WirelessCameraVlanRuleTests.cs
    │   │   └── WirelessIotVlanRuleTests.cs
    │   ├── Services/
    │   │   ├── DeviceTypeDetectionServiceTests.cs
    │   │   ├── FingerprintDetectorTests.cs
    │   │   ├── FirewallZoneLookupTests.cs
    │   │   └── MacOuiDetectorTests.cs
    │   └── xunit.runner.json
    ├── NetworkOptimizer.Core.Tests/
    │   ├── Caching/
    │   │   └── AsyncCachedValueTests.cs
    │   ├── Extensions/
    │   │   └── ServiceProviderExtensionsTests.cs
    │   ├── Helpers/
    │   │   ├── CloudflareIpRangesTests.cs
    │   │   ├── DisplayFormattersTests.cs
    │   │   └── NetworkUtilitiesTests.cs
    │   └── NetworkOptimizer.Core.Tests.csproj
    ├── NetworkOptimizer.Diagnostics.Tests/
    │   ├── Analyzers/
    │   │   ├── ApLockAnalyzerTests.cs
    │   │   ├── PerformanceAnalyzerTests.cs
    │   │   ├── PortProfile8021xAnalyzerTests.cs
    │   │   ├── PortProfileSuggestionAnalyzerTests.cs
    │   │   └── TrunkConsistencyAnalyzerTests.cs
    │   ├── DiagnosticsEngineTests.cs
    │   ├── NetworkOptimizer.Diagnostics.Tests.csproj
    │   └── xunit.runner.json
    ├── NetworkOptimizer.Monitoring.Tests/
    │   ├── AlertEngineTests.cs
    │   ├── AlertThresholdTests.cs
    │   ├── CellularModemStatsTests.cs
    │   ├── DeviceMetricsTests.cs
    │   ├── InterfaceMetricsTests.cs
    │   ├── MetricsAggregatorTests.cs
    │   ├── NetworkOptimizer.Monitoring.Tests.csproj
    │   ├── QmicliParserTests.cs
    │   └── SnmpConfigurationTests.cs
    ├── NetworkOptimizer.Reports.Tests/
    │   ├── BrandingOptionsTests.cs
    │   ├── NetworkOptimizer.Reports.Tests.csproj
    │   └── ReportDataTests.cs
    ├── NetworkOptimizer.Sqm.Tests/
    │   ├── BaselineCalculatorTests.cs
    │   ├── InputSanitizerTests.cs
    │   ├── LatencyMonitorTests.cs
    │   ├── NetworkOptimizer.Sqm.Tests.csproj
    │   ├── ScriptGeneratorTests.cs
    │   ├── SpeedtestIntegrationTests.cs
    │   ├── SqmManagerTests.cs
    │   └── WanInterfaceExtractionTests.cs
    ├── NetworkOptimizer.Storage.Tests/
    │   ├── AgentRepositoryTests.cs
    │   ├── AuditRepositoryTests.cs
    │   ├── CredentialProtectionServiceTests.cs
    │   ├── ModemRepositoryTests.cs
    │   ├── NetworkOptimizer.Storage.Tests.csproj
    │   ├── SettingsRepositoryTests.cs
    │   ├── SpeedTestRepositoryTests.cs
    │   ├── SqmRepositoryTests.cs
    │   ├── UniFiRepositoryTests.cs
    │   └── WanDataUsageServiceTests.cs
    ├── NetworkOptimizer.Threats.Tests/
    │   ├── BruteForceDetectorTests.cs
    │   ├── CrowdSecClientTests.cs
    │   ├── DDoSDetectorTests.cs
    │   ├── ExploitCampaignDetectorTests.cs
    │   ├── ExposureValidatorTests.cs
    │   ├── FlowInterestFilterTests.cs
    │   ├── KillChainClassifierTests.cs
    │   ├── NetworkOptimizer.Threats.Tests.csproj
    │   ├── ScanSweepDetectorTests.cs
    │   ├── ThreatEventNormalizerTests.cs
    │   └── ThreatPatternAnalyzerTests.cs
    ├── NetworkOptimizer.UniFi.Tests/
    │   ├── CgnatIpDetectionTests.cs
    │   ├── ClientIpEnricherTests.cs
    │   ├── DaisyChainPathTests.cs
    │   ├── DeviceTypeClassificationTests.cs
    │   ├── DiscoveredClientTests.cs
    │   ├── Fixtures/
    │   │   ├── NetworkTestData.cs
    │   │   └── TopologyBuilder.cs
    │   ├── GatewayApExclusionTests.cs
    │   ├── LagSpeedTests.cs
    │   ├── NetworkOptimizer.UniFi.Tests.csproj
    │   ├── NetworkPathAnalyzerIntegrationTests.cs
    │   ├── NetworkPathAnalyzerTests.cs
    │   ├── NetworkPathTests.cs
    │   ├── PathAnalysisResultTests.cs
    │   ├── PathTrace/
    │   │   └── BuildHopListTests.cs
    │   ├── RadioFormatHelperTests.cs
    │   ├── SnapshotIntegrationTests.cs
    │   ├── UniFiClientResponseTests.cs
    │   ├── UniFiFingerprintDatabaseTests.cs
    │   ├── UniFiFirewallZoneTests.cs
    │   ├── UniFiNetworkConfigTests.cs
    │   ├── UniFiProductDatabaseTests.cs
    │   ├── UniFiWlanConfigTests.cs
    │   └── WirelessRateSnapshotTests.cs
    ├── NetworkOptimizer.Web.Tests/
    │   ├── NetworkOptimizer.Web.Tests.csproj
    │   ├── WanSteerDeploymentServiceTests.cs
    │   └── WanSteerValidationTests.cs
    └── NetworkOptimizer.WiFi.Tests/
        ├── BssidIdentifierTests.cs
        ├── ChannelRecommendationServiceTests.cs
        ├── ChannelSpanHelperTests.cs
        ├── CoChannelInterferenceRuleTests.cs
        ├── CoverageGapRuleTests.cs
        ├── LoadImbalanceRuleTests.cs
        ├── NetworkOptimizer.WiFi.Tests.csproj
        ├── PropagationInterferenceTests.cs
        ├── RegulatoryChannelDataTests.cs
        ├── SignalClassificationTests.cs
        └── WiFiAnalysisHelpersTests.cs

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
# Git
.git
.gitignore
.gitattributes

# IDE
.vs
.vscode
.idea
*.user
*.suo

# Build outputs
**/bin
**/obj
**/out

# Test results
**/TestResults
**/coverage

# Temporary files
**/tmp
**/temp
*.tmp
*.log

# OS files
.DS_Store
Thumbs.db

# Node (if any frontend build tools)
**/node_modules

# Docker
docker/data
docker/logs
docker/ssh-keys
.env
*.env.local

# Documentation build
docs/_site

# Secrets (never include)
*.pfx
*.key
*.pem
credentials.json
secrets.json


================================================
FILE: .editorconfig
================================================
# EditorConfig for NetworkOptimizer
# https://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.{cs,csx}]
# Organize usings
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false

# Remove unused usings
dotnet_diagnostic.IDE0005.severity = warning

# Namespace preferences
csharp_style_namespace_declarations = file_scoped:suggestion

# var preferences
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# Expression-bodied members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion

# Null checking
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Braces
csharp_prefer_braces = true:suggestion

# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true

[*.{json,yml,yaml}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab


================================================
FILE: .github/FUNDING.yml
================================================
github: tvancott42
ko_fi: tjtuna42


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.200'

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: false

      - name: Build cfspeedtest
        working-directory: src/cfspeedtest
        run: go build -trimpath ./...

      - name: Build cfspeedtest (arm64 cross-compile)
        working-directory: src/cfspeedtest
        run: GOOS=linux GOARCH=arm64 go build -trimpath ./...

      - name: Build uwnspeedtest
        working-directory: src/uwnspeedtest
        run: go build -trimpath ./...

      - name: Build uwnspeedtest (arm64 cross-compile)
        working-directory: src/uwnspeedtest
        run: GOOS=linux GOARCH=arm64 go build -trimpath ./...

      - name: Build wansteer
        working-directory: src/wansteer
        run: go build -trimpath ./...

      - name: Build wansteer (arm64 cross-compile)
        working-directory: src/wansteer
        run: GOOS=linux GOARCH=arm64 go build -trimpath ./...

      - name: Test wansteer
        working-directory: src/wansteer
        run: go test ./...

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore --configuration Release

      - name: Test
        run: dotnet test --no-build --configuration Release --verbosity normal
        env:
          FLUENT_ASSERTIONS_LICENSED: ${{ secrets.FLUENT_ASSERTIONS_LICENSED }}


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    tags:
      - 'v*'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ozark-connect/network-optimizer
  SPEEDTEST_IMAGE_NAME: ozark-connect/speedtest

jobs:
  build:
    strategy:
      fail-fast: true
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set platform pair
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Extract version from tag
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./docker/Dockerfile
          platforms: ${{ matrix.platform }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            VERSION=${{ steps.version.outputs.version }}
          outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
          cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
          cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }},mode=max

      - name: Export digest
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: digests-${{ env.PLATFORM_PAIR }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Download digests
        uses: actions/download-artifact@v4
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Create manifest list and push
        working-directory: /tmp/digests
        run: |
          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)

      - name: Inspect image
        run: |
          docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

      # Speedtest image - trivial build (copy files into nginx:alpine), QEMU is fine
      - name: Extract metadata for speedtest image
        id: speedtest-meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.SPEEDTEST_IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and push speedtest image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./docker/openspeedtest/Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.speedtest-meta.outputs.tags }}
          labels: ${{ steps.speedtest-meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  publish-release:
    needs: merge
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Publish draft release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Find draft release matching this tag and publish it
          gh release edit ${{ github.ref_name }} --draft=false --repo ${{ github.repository }} || echo "No draft release found for ${{ github.ref_name }}"


================================================
FILE: .gitignore
================================================
# Build outputs
bin/
obj/
out/
publish/

# Go build artifacts
src/uwnspeedtest/uwnspeedtest*
src/cfspeedtest/cfspeedtest*

# IDE
.vs/
.vscode/
*.user
*.suo

# .NET
*.dll
*.pdb
*.cache

# Docker data
docker/data/
docker/logs/

# Per-host compose overrides (local customizations not checked in)
docker/docker-compose.override.yml

# Secrets
*.env
!*.env.example
docker/.env
ssh-keys/

# OS files
.DS_Store
Thumbs.db
nul

# Temp files
*.tmp
*.log

# Internal development notes
CLAUDE.md
.claude/
tmpclaude-*
plans/

# Local research work / scratch
research/
code-review/

# Local development scripts
scripts/local-dev/

# Archived code
archive/

# Coverage reports
coverage/
TestResults/

# Backup
backup/


================================================
FILE: Directory.Build.props
================================================
<Project>
  <ItemGroup>
    <PackageReference Include="MinVer" Version="6.*" PrivateAssets="all" />
  </ItemGroup>
  <PropertyGroup>
    <!-- MinVer derives version from git tags (e.g., v0.7.3 -> 0.7.3) -->
    <MinVerTagPrefix>v</MinVerTagPrefix>
  </PropertyGroup>
</Project>


================================================
FILE: LICENSE
================================================
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
"Business Source License" is a trademark of MariaDB Corporation Ab.

Parameters

Licensor:             Ozark Connect
Licensed Work:        Network Optimizer for UniFi. The Licensed Work is (c) 2026
                      Ozark Connect.
Additional Use Grant: You may make production use of the Licensed Work for personal,
                      non-commercial purposes on up to three sites. 
                      
                      Commercial use, including but not limited to use by managed 
                      service providers (MSPs), network installers, or any entity 
                      using the Licensed Work in the delivery of paid services to 
                      clients, requires a separate commercial license from the 
                      Licensor.
                      
                      For commercial licensing inquiries, please contact 
                      tj@ozarkconnect.net.
Change Date:          2028-01-01
Change License:       Apache License 2.0

For information about alternative licensing arrangements for the Licensed Work,
please contact tj@ozarkconnect.net.

Notice

Business Source License 1.1

Terms

The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited production use.

Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.

If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.

All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.

You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.

Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.

This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).

TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.

================================================
FILE: NetworkOptimizer.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Core", "src\NetworkOptimizer.Core\NetworkOptimizer.Core.csproj", "{A1B2C3D4-0001-0001-0001-000000000001}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Storage", "src\NetworkOptimizer.Storage\NetworkOptimizer.Storage.csproj", "{A1B2C3D4-0002-0002-0002-000000000002}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.UniFi", "src\NetworkOptimizer.UniFi\NetworkOptimizer.UniFi.csproj", "{A1B2C3D4-0003-0003-0003-000000000003}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Audit", "src\NetworkOptimizer.Audit\NetworkOptimizer.Audit.csproj", "{A1B2C3D4-0004-0004-0004-000000000004}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Sqm", "src\NetworkOptimizer.Sqm\NetworkOptimizer.Sqm.csproj", "{A1B2C3D4-0005-0005-0005-000000000005}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Monitoring", "src\NetworkOptimizer.Monitoring\NetworkOptimizer.Monitoring.csproj", "{A1B2C3D4-0006-0006-0006-000000000006}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Agents", "src\NetworkOptimizer.Agents\NetworkOptimizer.Agents.csproj", "{A1B2C3D4-0007-0007-0007-000000000007}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Reports", "src\NetworkOptimizer.Reports\NetworkOptimizer.Reports.csproj", "{A1B2C3D4-0008-0008-0008-000000000008}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Web", "src\NetworkOptimizer.Web\NetworkOptimizer.Web.csproj", "{A1B2C3D4-0009-0009-0009-000000000009}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Audit.Tests", "tests\NetworkOptimizer.Audit.Tests\NetworkOptimizer.Audit.Tests.csproj", "{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Storage.Tests", "tests\NetworkOptimizer.Storage.Tests\NetworkOptimizer.Storage.Tests.csproj", "{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Monitoring.Tests", "tests\NetworkOptimizer.Monitoring.Tests\NetworkOptimizer.Monitoring.Tests.csproj", "{6E45D264-3A7D-40EB-9B5E-C1685212B561}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.UniFi.Tests", "tests\NetworkOptimizer.UniFi.Tests\NetworkOptimizer.UniFi.Tests.csproj", "{6BCA4A03-EC08-48D5-9789-0F23C416B062}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Core.Tests", "tests\NetworkOptimizer.Core.Tests\NetworkOptimizer.Core.Tests.csproj", "{D24105B5-B804-4E55-9064-98179F6DFBF2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Sqm.Tests", "tests\NetworkOptimizer.Sqm.Tests\NetworkOptimizer.Sqm.Tests.csproj", "{E8182317-73B2-4196-B628-4747C11A238D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Agents.Tests", "tests\NetworkOptimizer.Agents.Tests\NetworkOptimizer.Agents.Tests.csproj", "{E4902895-D017-4B52-B024-53F9FC237CF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Reports.Tests", "tests\NetworkOptimizer.Reports.Tests\NetworkOptimizer.Reports.Tests.csproj", "{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Diagnostics", "src\NetworkOptimizer.Diagnostics\NetworkOptimizer.Diagnostics.csproj", "{58377D73-D053-4EF0-99B2-14F6E9547ED4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Diagnostics.Tests", "tests\NetworkOptimizer.Diagnostics.Tests\NetworkOptimizer.Diagnostics.Tests.csproj", "{9F192F42-4B9A-49F3-99E9-273298D5AC93}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.WiFi", "src\NetworkOptimizer.WiFi\NetworkOptimizer.WiFi.csproj", "{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.WiFi.Tests", "tests\NetworkOptimizer.WiFi.Tests\NetworkOptimizer.WiFi.Tests.csproj", "{EEF0B083-6131-4C4E-96AD-FC9EA571E941}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Alerts", "src\NetworkOptimizer.Alerts\NetworkOptimizer.Alerts.csproj", "{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Alerts.Tests", "tests\NetworkOptimizer.Alerts.Tests\NetworkOptimizer.Alerts.Tests.csproj", "{45AED52D-E4D4-40FE-B310-433B93853F1C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Threats", "src\NetworkOptimizer.Threats\NetworkOptimizer.Threats.csproj", "{D23999B0-B2F7-4DD9-AA35-09F385E36726}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Threats.Tests", "tests\NetworkOptimizer.Threats.Tests\NetworkOptimizer.Threats.Tests.csproj", "{AC78B418-5216-49F6-9084-BB4A0241A2DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkOptimizer.Web.Tests", "tests\NetworkOptimizer.Web.Tests\NetworkOptimizer.Web.Tests.csproj", "{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Debug|x64 = Debug|x64
		Debug|x86 = Debug|x86
		Release|Any CPU = Release|Any CPU
		Release|x64 = Release|x64
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0001-0001-0001-000000000001}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0002-0002-0002-000000000002}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0003-0003-0003-000000000003}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0004-0004-0004-000000000004}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0005-0005-0005-000000000005}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0006-0006-0006-000000000006}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0007-0007-0007-000000000007}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0008-0008-0008-000000000008}.Release|x86.Build.0 = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|x64.Build.0 = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Debug|x86.Build.0 = Debug|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|Any CPU.Build.0 = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|x64.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|x64.Build.0 = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|x86.ActiveCfg = Release|Any CPU
		{A1B2C3D4-0009-0009-0009-000000000009}.Release|x86.Build.0 = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|x64.ActiveCfg = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|x64.Build.0 = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|x86.ActiveCfg = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Debug|x86.Build.0 = Debug|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|Any CPU.Build.0 = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|x64.ActiveCfg = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|x64.Build.0 = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|x86.ActiveCfg = Release|Any CPU
		{0F787DF2-4792-43F8-89F8-1DA862AD9FE6}.Release|x86.Build.0 = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|x64.ActiveCfg = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|x64.Build.0 = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|x86.ActiveCfg = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Debug|x86.Build.0 = Debug|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|Any CPU.Build.0 = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|x64.ActiveCfg = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|x64.Build.0 = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|x86.ActiveCfg = Release|Any CPU
		{5315FA3C-19CC-41FE-BF3E-3E20351AB9BF}.Release|x86.Build.0 = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|x64.ActiveCfg = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|x64.Build.0 = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|x86.ActiveCfg = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Debug|x86.Build.0 = Debug|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|Any CPU.Build.0 = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|x64.ActiveCfg = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|x64.Build.0 = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|x86.ActiveCfg = Release|Any CPU
		{6E45D264-3A7D-40EB-9B5E-C1685212B561}.Release|x86.Build.0 = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|x64.ActiveCfg = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|x64.Build.0 = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|x86.ActiveCfg = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Debug|x86.Build.0 = Debug|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|Any CPU.Build.0 = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|x64.ActiveCfg = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|x64.Build.0 = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|x86.ActiveCfg = Release|Any CPU
		{6BCA4A03-EC08-48D5-9789-0F23C416B062}.Release|x86.Build.0 = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|x64.ActiveCfg = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|x64.Build.0 = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|x86.ActiveCfg = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Debug|x86.Build.0 = Debug|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|Any CPU.Build.0 = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|x64.ActiveCfg = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|x64.Build.0 = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|x86.ActiveCfg = Release|Any CPU
		{D24105B5-B804-4E55-9064-98179F6DFBF2}.Release|x86.Build.0 = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|x64.ActiveCfg = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|x64.Build.0 = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|x86.ActiveCfg = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Debug|x86.Build.0 = Debug|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|Any CPU.Build.0 = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|x64.ActiveCfg = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|x64.Build.0 = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|x86.ActiveCfg = Release|Any CPU
		{E8182317-73B2-4196-B628-4747C11A238D}.Release|x86.Build.0 = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|x64.ActiveCfg = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|x64.Build.0 = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|x86.ActiveCfg = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Debug|x86.Build.0 = Debug|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|Any CPU.Build.0 = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|x64.ActiveCfg = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|x64.Build.0 = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|x86.ActiveCfg = Release|Any CPU
		{E4902895-D017-4B52-B024-53F9FC237CF5}.Release|x86.Build.0 = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|x64.ActiveCfg = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|x64.Build.0 = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|x86.ActiveCfg = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Debug|x86.Build.0 = Debug|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|Any CPU.Build.0 = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|x64.ActiveCfg = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|x64.Build.0 = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|x86.ActiveCfg = Release|Any CPU
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B}.Release|x86.Build.0 = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|x64.ActiveCfg = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|x64.Build.0 = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|x86.ActiveCfg = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Debug|x86.Build.0 = Debug|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|Any CPU.Build.0 = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|x64.ActiveCfg = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|x64.Build.0 = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|x86.ActiveCfg = Release|Any CPU
		{58377D73-D053-4EF0-99B2-14F6E9547ED4}.Release|x86.Build.0 = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|x64.ActiveCfg = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|x64.Build.0 = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|x86.ActiveCfg = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Debug|x86.Build.0 = Debug|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|Any CPU.Build.0 = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|x64.ActiveCfg = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|x64.Build.0 = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|x86.ActiveCfg = Release|Any CPU
		{9F192F42-4B9A-49F3-99E9-273298D5AC93}.Release|x86.Build.0 = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|x64.ActiveCfg = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|x64.Build.0 = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|x86.ActiveCfg = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Debug|x86.Build.0 = Debug|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|Any CPU.Build.0 = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|x64.ActiveCfg = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|x64.Build.0 = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|x86.ActiveCfg = Release|Any CPU
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E}.Release|x86.Build.0 = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|x64.ActiveCfg = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|x64.Build.0 = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|x86.ActiveCfg = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Debug|x86.Build.0 = Debug|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|Any CPU.Build.0 = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|x64.ActiveCfg = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|x64.Build.0 = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|x86.ActiveCfg = Release|Any CPU
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941}.Release|x86.Build.0 = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|x64.ActiveCfg = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|x64.Build.0 = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|x86.ActiveCfg = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Debug|x86.Build.0 = Debug|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|Any CPU.Build.0 = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|x64.ActiveCfg = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|x64.Build.0 = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|x86.ActiveCfg = Release|Any CPU
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28}.Release|x86.Build.0 = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|x64.ActiveCfg = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|x64.Build.0 = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|x86.ActiveCfg = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Debug|x86.Build.0 = Debug|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|Any CPU.Build.0 = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|x64.ActiveCfg = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|x64.Build.0 = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|x86.ActiveCfg = Release|Any CPU
		{45AED52D-E4D4-40FE-B310-433B93853F1C}.Release|x86.Build.0 = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|x64.ActiveCfg = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|x64.Build.0 = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|x86.ActiveCfg = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Debug|x86.Build.0 = Debug|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|Any CPU.Build.0 = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|x64.ActiveCfg = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|x64.Build.0 = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|x86.ActiveCfg = Release|Any CPU
		{D23999B0-B2F7-4DD9-AA35-09F385E36726}.Release|x86.Build.0 = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|x64.ActiveCfg = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|x64.Build.0 = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|x86.ActiveCfg = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Debug|x86.Build.0 = Debug|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|Any CPU.Build.0 = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|x64.ActiveCfg = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|x64.Build.0 = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|x86.ActiveCfg = Release|Any CPU
		{AC78B418-5216-49F6-9084-BB4A0241A2DA}.Release|x86.Build.0 = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|x64.ActiveCfg = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|x64.Build.0 = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|x86.ActiveCfg = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Debug|x86.Build.0 = Debug|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|Any CPU.Build.0 = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|x64.ActiveCfg = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|x64.Build.0 = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|x86.ActiveCfg = Release|Any CPU
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E}.Release|x86.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(NestedProjects) = preSolution
		{6BCA4A03-EC08-48D5-9789-0F23C416B062} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{D24105B5-B804-4E55-9064-98179F6DFBF2} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{E8182317-73B2-4196-B628-4747C11A238D} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{E4902895-D017-4B52-B024-53F9FC237CF5} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{BF01305D-EC29-40DA-B9E4-B4E29FDB601B} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{58377D73-D053-4EF0-99B2-14F6E9547ED4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
		{9F192F42-4B9A-49F3-99E9-273298D5AC93} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{7E555A86-2585-4D7A-BBB5-E4F71D14FD0E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
		{EEF0B083-6131-4C4E-96AD-FC9EA571E941} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{EDDEBF6E-19A7-46F4-8BA4-FDFF5F4D5F28} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
		{45AED52D-E4D4-40FE-B310-433B93853F1C} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{D23999B0-B2F7-4DD9-AA35-09F385E36726} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
		{AC78B418-5216-49F6-9084-BB4A0241A2DA} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
		{EC72C9AD-625C-4AA8-A7CC-744515E06F1E} = {5691A6DD-53B9-4CE0-A3C9-3D4F815E2120}
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {F7E6D5C4-B3A2-9180-7F6E-5D4C3B2A1908}
	EndGlobalSection
EndGlobal


================================================
FILE: README.md
================================================
<p align="center">
  <img src="docs/images/app-logo-v2.png" alt="Network Optimizer" width="200">
</p>

# Network Optimizer for UniFi

[![GitHub Release](https://img.shields.io/github/v/release/Ozark-Connect/NetworkOptimizer)](https://github.com/Ozark-Connect/NetworkOptimizer/releases)
[![Docker Pulls](https://img.shields.io/badge/docker_pulls-149k-blue?logo=docker)](https://github.com/orgs/Ozark-Connect/packages?repo_name=NetworkOptimizer)
[![Windows Downloads](https://img.shields.io/github/downloads/Ozark-Connect/NetworkOptimizer/total?label=windows%20downloads)](https://github.com/Ozark-Connect/NetworkOptimizer/releases)
[![GitHub last commit](https://img.shields.io/github/last-commit/Ozark-Connect/NetworkOptimizer)](https://github.com/Ozark-Connect/NetworkOptimizer/commits)
[![GitHub Stars](https://img.shields.io/github/stars/Ozark-Connect/NetworkOptimizer)](https://github.com/Ozark-Connect/NetworkOptimizer/stargazers)
[![License](https://img.shields.io/badge/license-BSL_1.1-green)](https://github.com/Ozark-Connect/NetworkOptimizer/blob/main/LICENSE)

## THANK YOU to all of my Sponsors

Genuinely, thank you so much to everybody for taking the time to use Network Optimizer and have it find a place on your network(s). It really means a lot to receive all of the bug reports, feature requests, feedback, support, and donations from everybody. Totally a whole new experience from writing code in a dayjob, and it greatly motivates me to keep on going!

## New: API Key auth to console

Connect to your UniFi Console using an API key instead of username and password. Generated in UniFi Network under Integrations -> Create New API Key. The key is encrypted at rest and never exposed in logs or the UI. Useful for sites where you don't necessarily want to create a Local Admin, or when you're using UniFi Fabrics which no longer lets you create Local Admin users.

## New: WAN Steering

UniFi makes you choose between WAN Failover and Load Balancing, and its Policy-Based Routes can only match by destination IP or domain - not port or protocol. WAN Steering removes both limitations. Keep your primary WAN for responsive, latency-sensitive traffic by default, and selectively load balance bulk traffic - Steam downloads, OS updates, Xbox downloads - across your secondary connections so they're not just sitting idle waiting for a failover event.

Route by source, destination, port, or protocol with full load balancing support. Pin gaming traffic to your fastest link while HTTP/HTTPS flows get split 50/50 across all your WANs. Health-check failover, automatic rule recovery after gateway reprovisioning, and zero impact to gateway performance.

## New: HTTPS Reverse Proxy

Enable HTTPS with automatic Let's Encrypt certificates using the included [Traefik reverse proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy). It forces HTTP/1.1 for speed tests (HTTP/2 multiplexing skews results) while keeping HTTP/2 for the main app. Windows MSI users can enable Traefik as an optional feature during install. HTTPS also unlocks GPS-based tagging on your self-hosted Speed Test and Signal walk test data, since browsers require a secure context for location access.

## New: Threat Intelligence

Your UniFi gateway's IPS is blocking threats all day long, but the UniFi Console buries this data in a flat event log with no context. Threat Intelligence pulls those IPS events and actually analyzes them: who's attacking you, where they're coming from, what they're after, and whether it's random noise or a coordinated effort.

The exposure analysis is where it gets useful. It cross-references your port forwards with actual threat data, so you can see which of your exposed services are getting hammered and from where. Attack sequence detection watches for the same source IP progressing through kill chain stages (reconnaissance to exploitation to post-exploitation) and flags the ones that look like real campaigns rather than drive-by scanning. Geographic and ASN breakdowns show you which countries and networks are generating the most traffic against your infrastructure.

CrowdSec CTI integration adds reputation scoring and MITRE ATT&CK classification to each source IP, so you're not just looking at raw events - you know whether that IP has a history of malicious activity across the broader internet.

## New: Alerts & Scheduling

Set up automated speed tests and security audits on a schedule, and get notified when something goes wrong. The scheduling engine handles recurring WAN and LAN speed tests with configurable frequency and time windows, plus periodic security audits that track your score over time.

Alert rules watch for the things that matter: audit score drops, WAN speed degradation, LAN speed regression against recent baselines, IPS attack chains reaching active exploitation, and scheduled task failures. Each rule has configurable severity thresholds and cooldown periods so you're not drowning in noise. Threshold-based rules (like "alert me when WAN speed drops 40% below the recent average") let you tune sensitivity to your environment.

Delivery channels support email (SMTP with STARTTLS), Discord, Slack, Microsoft Teams, and generic webhooks. Low-priority alerts can be set to digest-only mode so they get bundled into a daily summary instead of pinging you every time your neighbor microwaves lunch and your 2.4 GHz channel gets congested.
## New: Client Performance

A per-device analytics dashboard for any client on your network. Pick a device and get live signal monitoring, speed test history with download/upload trends, latency and jitter charts, network path visualization showing every hop and bottleneck link, and a connection timeline tracking AP roams and disconnects. Walk around with the page open on your phone (over HTTPS) and it builds a GPS-based signal heatmap of your actual coverage. Three tabs - Speed, Signal, and Connection - give you everything you need to troubleshoot why a device is slow or unstable.

---

You've set up VLANs, configured firewall rules, maybe even deployed a Pi-hole for DNS filtering. The UniFi controller gives you all this power, but it never actually tells you whether your configuration is any good. Are your firewall rules doing what you think they're doing? Is that IoT VLAN actually isolated, or did you miss something? When a device bypasses your DNS settings and phones home directly, would you even know?

Network Optimizer answers those questions. It connects to your UniFi controller, analyzes your configuration, and tells you what's working, what's broken, and what you should fix. No more guessing.

## Main Features

### Wi-Fi Optimizer & Signal Map

Site health scoring, RF environment analysis, client stats, roaming tracking, band steering, and airtime fairness across twelve analysis tabs. The Channel Recommendation engine models pairwise AP interference using signal propagation, live RF scan data, and triangulated neighbor networks, then factors in historical channel stress (utilization, interference, TX retries) to find the lowest-interference channel assignment across your entire network. It respects mesh uplink constraints, DFS preferences, and regulatory channel availability, and validates every recommended move against improvement thresholds so it won’t suggest changes that aren’t worth the disruption.

On the client side, you get a sortable, searchable table view with online/offline filtering, per-client signal and roaming history, and band-segmented Wi-Fi generation breakdowns showing exactly where your airtime is going. Environmental correlation heatmaps surface interference patterns by time of day and day of week, and every recommendation includes the specific UniFi Network UI navigation path to apply the change.

Signal Map lets you draw your building layout, place APs, and see a real-time RF propagation heatmap. Supports wall materials (drywall, concrete, glass, etc.), multi-floor buildings with cross-floor signal propagation, and per-AP antenna patterns pulled from your controller. Simulate TX power and antenna mode changes to see how they’d affect coverage before touching your actual config. Add planned APs to simulate coverage before buying or mounting hardware.

### Security Auditing

The audit engine runs 83 security checks across five categories and scores your network 0-100. This isn't a checkbox audit that just confirms you have a firewall; it actually analyzes what your rules do and whether they're doing it correctly.

Firewall analysis catches the subtle stuff: rules that shadow each other, allow rules that subvert your deny rules, allow rules that punch holes through your network isolation. VLAN security checks whether your IoT devices and cameras are actually on the networks you intended (using UniFi fingerprints, MAC OUI lookup, and port naming patterns). DNS security validates your DoH configuration, checks for bypass routes (including DoT, DoQ, and HTTP/3 DoH bypass), and verifies that your WAN interface DNS settings match what you configured. Port security looks at MAC restrictions, port isolation, and whether you've left unused ports enabled. UPnP analysis flags enabled UPnP, exposed privileged ports, and static port forwards you may have forgotten about.

You get a score, a breakdown by severity (critical, recommended, informational), and specific recommendations for each issue. Dismiss false positives if your setup is intentional, export PDF reports for documentation, track your score over time.

### WAN Steering

UniFi's WAN Failover keeps secondary connections idle until your primary goes down. Load Balancing splits everything across all WANs but gives you no control over what goes where. WAN Steering lets you have both: keep your primary WAN as the default for latency-sensitive traffic, and selectively load balance bulk traffic across your secondary connections with full port and protocol matching.

Define traffic classes by source, destination, port, or protocol, assign them to specific WANs or load balance across multiple WANs with configurable weight. A lightweight Go binary on your gateway inserts rules above UniFi's routing table, watches for WAN state changes and reprovisioning events, and recovers automatically. Health-check failover still works as expected - if a WAN goes down, traffic redistributes to healthy links.

### Adaptive SQM

If you're on cable, DSL, or cellular, you know bufferbloat. That lag spike when someone starts a download or joins a video call. SQM fixes it, but setting the bandwidth limits correctly is a guessing game; too high and SQM can't shape traffic effectively, too low and you're leaving speed on the table.

Network Optimizer handles this automatically. It supports dual-WAN with independent configuration per interface, connection profiles tuned for DOCSIS, fiber, wireless, Starlink, and cellular (each has different characteristics that matter). Scheduled speedtests adjust your rates based on actual measured performance. Latency monitoring backs off when congestion appears. One-click deployment pushes the configuration to your UDM or UCG gateway with persistence through reboots.

### WAN Speed Testing

Test your internet connection speed directly from the server. Measures download, upload, latency, loaded latency (bufferbloat detection), and jitter with full history and per-WAN connection tracking. Results are plotted in time-series charts filterable by connection, so you can compare providers and track performance over time across multi-WAN setups.

Also includes a standalone OpenSpeedTest server you can host on a VPS or remote machine, so you can run WAN speed tests against your own private infrastructure instead of relying on third-party speed test services. Configure it in Settings and get a ready-to-copy deploy command - see [External WAN Speed Test Server](docker/DEPLOYMENT.md#external-wan-speed-test-server-optional) in the deployment guide. If you're that kind of nerd.

### LAN Speed Testing

Ever wonder if that new switch is actually delivering 10 gigabit speeds? Or whether the cable run to the shop is the bottleneck?

Network Optimizer runs iperf3 tests between your gateway and network devices, auto-discovers UniFi equipment from your controller, supports custom devices with per-device SSH credentials, auto indexes iperf3 results from tests initiated by other devices against the built in server (if enabled), and correlates results with hop count and infrastructure path, with detailed Wi-Fi stats and link speeds recorded along with UniFi firmware versions.

Test history lets you track performance over time with these relevant data in order to identify and characterize any changes to performance.

![LAN Speed Test](docs/images/lan-speed-test.png)

### Client Speed Testing

Test LAN speeds from any device without SSH access. Open a browser on your phone, tablet, or laptop and run a speed test; results are automatically recorded with device identification. For CLI users, the bundled iperf3 server accepts client connections and logs results. See [Client Speed Testing](docker/DEPLOYMENT.md#client-speed-testing-optional) in the deployment guide.

![Client Speed Test with Network Path](docs/images/client-speed-test-trace.png)

With HTTPS enabled, browser tests can collect location data (with permission) to build a Speed / Coverage Map showing real-world performance across your property or campus.

![Speed / Coverage Map](docs/images/speed-coverage-map.png)

### Cellular Modem Monitoring

If you're running a U-LTE or U5G-Max for backup (or primary) connectivity, you can monitor signal quality from the dashboard: RSRP, RSRQ, SNR, cell tower info, and connection status. Supports multiple modems with easy navigation between them.

![Cellular Stats Demo](docs/images/cellular-stats.gif)

### UPnP Inspector

Ever wonder what ports your network is actually exposing to the internet? Your Xbox, Plex server, and smart home devices are all punching holes through your firewall via UPnP, and UniFi doesn't make it easy to see what's going on.

The UPnP Inspector puts it all in one place: every dynamic UPnP mapping and static port forward, grouped by device, with color-coded status so you can see at a glance what's active, what's idle, and what's about to expire. Add notes to remember what each mapping is for (because you will forget). Search and filter when you're hunting for that one port that's causing problems.

### Coming Soon

Cable modem stats (signal levels, uncorrectables, T3/T4 timeouts) for those of you fighting with your ISP about line quality.

## Requirements

- UniFi Console (aka Controller) - UDM, UCG, UDR, CloudKey, or self-hosted UniFi Network Server
- Network access to your UniFi Console API (HTTPS)

Most features work with just API access. SSH is only needed for speed testing and Adaptive SQM:

| Feature | SSH needed? |
|---------|------------|
| Security Audit | No |
| Config Optimizer | No, but Gateway SSH required for upcoming features |
| Wi-Fi Optimizer | No |
| Threat Intelligence | No |
| Alerts & Scheduling | No (schedules speed tests that may require SSH) |
| Client Speed Test | No |
| WAN Speed Test | No, but gateway-based requires Gateway SSH |
| LAN Speed Test | Yes - Gateway SSH and/or Device SSH |
| WAN Steering | Yes - Gateway SSH |
| Adaptive SQM | Yes - Gateway SSH |

To enable SSH, see [SSH Configuration](docker/DEPLOYMENT.md#unifi-ssh-configuration) in the Deployment Guide. SSH must be configured via the UniFi web interface (not the mobile app).

## Installation

| Platform | Method | Guide |
|----------|--------|-------|
| Linux Server | Docker (recommended) | [Deployment Guide](docker/DEPLOYMENT.md#1-linux--docker-recommended) |
| Proxmox VE | LXC one-liner | [Proxmox Guide](scripts/proxmox/README.md) |
| Synology/QNAP/Unraid | Docker | [NAS Deployment](docker/DEPLOYMENT.md#3-nas-deployment-docker) |
| Home Assistant | Add-ons | [Home Assistant](docker/DEPLOYMENT.md#5-home-assistant) |
| Windows | Installer (recommended) | [Download from Releases](https://github.com/Ozark-Connect/NetworkOptimizer/releases) |
| macOS | Native (best performance) | [macOS Installation](docs/MACOS-INSTALLATION.md) |
| Linux | Native (no Docker) | [Linux Native](docker/NATIVE-DEPLOYMENT.md#linux-deployment) |

Docker Desktop on macOS and Windows limits network throughput for speed testing. For accurate multi-gigabit measurements, use native deployment.

### HTTPS Reverse Proxy

For HTTPS with automatic Let's Encrypt certificates, use [NetworkOptimizer-Proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy) - a Traefik setup that forces HTTP/1.1 for speed tests (HTTP/2 multiplexing skews results) while keeping HTTP/2 for the main app. Proxmox LXC and Windows MSI users can enable Traefik as an optional feature during install. This also allows for simpler enablement of GPS-based tagging on your self-hosted Speed Test and Signal walk test data as browsers require HTTPS for location data to flow.

### Quick Start (Linux Docker)

**Option A: Pull Docker Image (Recommended)**

```bash
mkdir network-optimizer && cd network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
docker compose up -d

# Check logs for the auto-generated admin password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

**Option B: Build from Source**

```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
cp .env.example .env
docker compose build
docker compose up -d

# Check logs for the auto-generated admin password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

Open http://localhost:8042

### Quick Start (Proxmox)

```bash
bash -c "$(wget -qLO - https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/proxmox/install.sh)"
```

### First Run

1. Go to Settings and enter your UniFi controller URL
2. Create a **Local Access Only** account on your controller (Ubiquiti SSO won't work):
   - Quick: Super Admin role
   - Restricted: Network View Only, Protect View Only, User Management None
   - See the in-app setup guide or [detailed instructions](docker/DEPLOYMENT.md#unifi-account)
3. Click Connect to authenticate
4. Navigate to Audit to run your first security scan

## Project Structure

```
src/
├── NetworkOptimizer.Web         # Blazor web UI
├── NetworkOptimizer.Alerts      # Alerts & Scheduling engine
├── NetworkOptimizer.Audit       # Security Audit
├── NetworkOptimizer.Core        # Shared helpers and utilities
├── NetworkOptimizer.Diagnostics # Config Optimizer
├── NetworkOptimizer.Monitoring  # SNMP/SSH polling
├── NetworkOptimizer.Reports     # PDF/Markdown report generation
├── NetworkOptimizer.Sqm         # Adaptive SQM
├── NetworkOptimizer.Storage     # SQLite database
├── NetworkOptimizer.Threats     # Threat Intelligence
├── NetworkOptimizer.UniFi       # UniFi API client
├── NetworkOptimizer.WiFi        # Wi-Fi Optimizer
├── cfspeedtest/                 # WAN Speed Test (binary for gateway)
└── OpenSpeedTest/               # Client Speed Test
```

## Tech Stack

.NET 10, Blazor Server, SQLite, iperf3, SSH.NET, QuestPDF, OpenSpeedTest™, Go (WAN speed test binary)

## Password Reset

If you forget the admin password, use the reset script for your platform:

**Docker / macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash
```

**Windows (PowerShell as Administrator):**
```powershell
irm https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.ps1 -OutFile reset-password.ps1
.\reset-password.ps1
```

The script stops the service, clears the password, restarts, and shows you the new temporary password.

## Contributing

If you find issues, report them via GitHub Issues. Include your UniFi device models and controller version. Sanitize credentials and IPs before attaching logs.

## License

Business Source License 1.1

**Licensor:** Ozark Connect

**Licensed Work:** Network Optimizer for UniFi

**Personal Use:** You may use the Licensed Work for personal, non-commercial purposes on up to three sites.

**Commercial Use:** Use by managed service providers (MSPs), network installers, IT consultants, or any entity using this software in the delivery of paid services requires a commercial license.

**Change Date:** January 1, 2028

**Change License:** Apache License 2.0

For commercial licensing inquiries, contact tj@ozarkconnect.net.

© 2026 Ozark Connect

## Support

- Issues: [GitHub Issues](https://github.com/Ozark-Connect/NetworkOptimizer/issues)
- Documentation: See component READMEs in `src/` and `docker/`

## Other Projects

- [UniFi Lightshow](https://github.com/Ozark-Connect/unifi-lightshow) - Custom RGB light show controller for UniFi Etherlighting LEDs. Turn your switch rack into a spatial light canvas with SignalRGB integration, seasonal effects, and multi-switch support.
- [UNVR NAS Backup](https://github.com/Ozark-Connect/unvr-nas-backup) - Automated Protect camera backup from UniFi NVR to NAS storage.

---

<sub>Network Optimizer for UniFi is an independent project by Ozark Connect and is not affiliated with, endorsed by, or sponsored by Ubiquiti, Inc. Ubiquiti, UniFi, UDM, and Cloud Key are trademarks or registered trademarks of Ubiquiti, Inc. All other trademarks are the property of their respective owners.</sub>


================================================
FILE: TODO.md
================================================
# Network Optimizer - TODO / Future Enhancements

## LAN Speed Test

### Path Analysis Enhancements
- ✅ ~~Direction-aware bottleneck calculation~~ (done - `GetDirectionalEfficiency()` in PathAnalysisResult, separate TX/RX bottleneck in NetworkPathAnalyzer)
- More gateway models in routing limits table as we gather data
- Threshold tuning based on real-world data collection
- **Consistent wireless bottleneck attribution across test types:** LAN client speed tests show the bottleneck relative to the AP (e.g., "[AP] Back Yard (wireless)") while WAN client speed tests show it relative to the client (e.g., "[Phone] TJ iPhone (wireless)"). This is because WAN client paths reverse hops and swap ingress/egress, which flips the perspective. The wireless link is the same physical connection - both descriptions are technically correct but inconsistent. Investigate unifying to always name the AP side, since that's what users can control. Relevant code: `CalculateWanClientPathAsync` hop reversal/swap and `CalculateBottleneck` wireless link attribution.

### ✅ ~~Scheduled LAN Speed Test~~ (done - Alerts & Scheduling feature)

### ✅ ~~Scheduled WAN Speed Test~~ (done - Alerts & Scheduling feature)

## Alerts & Scheduling

### ✅ ~~LAN Speed Test Schedule: UniFi Device Targets~~ (done)

### DST-Aware Schedule Time Display
- Schedule start times are stored as UTC hour/minute and converted to local for display using `DateTime.UtcNow.Date.ToLocalTime()`
- This uses the current day's DST offset, so a schedule created at 6:00 AM CDT (UTC-5) displays as 5:00 AM during CST (UTC-6)
- The read-only view (`FormatStartTime`) and edit form (`UtcToLocalTimeOnly`) are consistent with each other, but both shift by an hour across DST transitions
- Actual execution time is correct (UTC-based) - only the displayed local time drifts
- **Affected code:** `Alerts.razor: FormatStartTime()`, `UtcToLocalTimeOnly()`, `ParseTimeInput()`
- **Options:** Store IANA timezone per schedule, use `TimeZoneInfo.ConvertTimeFromUtc`, or store local time + timezone

### Threat Alert Dedup Tuning (if users report noise)

Current state (as of v1.5.x): Dedup is working - event-level dedup via InnerAlertId, pattern-level dedup via DedupKey with 6h merge window, rule-level cooldown at 1h. No spam reported yet, but here are levers to pull if it gets noisy:

**ScanSweep re-alerting for persistent scanners**
- Currently: Same IP re-alerts every ~2h if it keeps scanning (new events push LastSeen past LastAlertedAt, then 1h rule cooldown expires)
- Option A: Bump `attack_pattern` rule cooldown from 1h to 6h (matches the pattern merge window - one alert per scan window)
- Option B: Change `GetUnalertedPatternsAsync` to require event count increase (e.g., `EventCount > previousEventCount * 1.5`) instead of just `LastSeen > LastAlertedAt`
- Option C: Leave as-is - ongoing scanning is arguably worth periodic notification
- Trade-off: Less noise vs missing escalation of an ongoing scan that adds new ports

**DDoS alert cooldown key uses wrong IP**
- Currently: `DeviceIp = firstSourceIp` means the cooldown key is `{ruleId}:{randomSourceIp}`. For multi-source attacks (DDoS), the first source IP in the sorted list can shift between cycles, defeating cooldown.
- Fix: Use the target IP (from DedupKey `ddos:{targetIp}:{port}`) as DeviceIp for DDoS patterns, so cooldown groups by what's being attacked, not who's attacking
- Low priority since DDoS pattern dedup (DedupKey) now merges patterns correctly - this only matters if the pattern is re-detected after the 6h window

**Early-stage chain alert granularity**
- Currently: Re-alerts on more stages OR (6h elapsed AND 2x events). The `attack_chain_attempt` rule has 1h cooldown.
- If noisy: Increase cooldown to 6h, or only re-alert on stage progression (not event count growth)
- If too quiet: Reduce the 2x event multiplier to 1.5x
- These are Info severity - users who find them noisy can disable rule 13 in alert settings

## Security Audit / PDF Report

### Manual Network Purpose Override
- Allow users to manually set the purpose/classification of their Networks in Security Audit Settings
- Currently: Network purpose (IoT, Security, Guest, Management, etc.) is auto-detected from network name patterns
- Problem: Users with non-standard naming conventions get incorrect VLAN placement recommendations
- Implementation:
  - Add "Network Classifications" section to Security Audit Settings page
  - List all detected networks with current auto-detected purpose
  - Allow override via dropdown: Corporate, Home, IoT, Security, Guest, Management, Printer, Unknown
  - Store overrides in database (new table or extend existing settings)
  - VlanAnalyzer should check for user overrides before applying name-based detection
- Benefits:
  - Users with custom naming schemes can get accurate audits
  - Explicit classification removes ambiguity
  - Auto-detection still works as default for users who don't configure

### Home → IoT Return Traffic Rule Suggestion
- When Home network has isolation blocking IoT, suggest adding a return traffic rule or explicit allow
- **Problem:** If Home blocks all traffic to IoT (good for security), return traffic from IoT devices won't work
  - Example: Smart TV on IoT can't respond to casting from phone on Home
  - Example: IoT device can't respond to control commands from Home devices
- **Detection:** Check for block rule Home → IoT without a corresponding:
  - Allow rule Home → IoT (with specific IPs/devices/ports), OR
  - Return traffic allow rule IoT → Home (RESPOND_ONLY / ESTABLISHED,RELATED)
- **Recommendation options:**
  1. Add specific allow rules from Home to IoT devices that need control (e.g., smart TVs, speakers)
  2. Add a RESPOND_ONLY allow rule from IoT → Home to permit return traffic
- **Severity:** Informational (user may have intentionally blocked bidirectional)
- **Context:** This is a usability issue, not a security issue - blocking return traffic is actually more secure

### Third-Party DNS Firewall Rule Check
- When third-party DNS (Pi-hole, AdGuard, etc.) is detected on a network, check for a firewall rule blocking UDP 53 to the gateway
- Without this rule, clients could bypass third-party DNS by using the gateway directly
- Implementation: Look for firewall rules that DROP/REJECT UDP 53 from the affected VLANs to the gateway IP
- Severity: Recommended (not Critical, since some users intentionally allow fallback)
- **Status:** Awaiting user feedback on current third-party DNS feature before implementing

### ✅ ~~Printer/Scanner Audit Logic Consolidation~~ (done)
- Consolidated in `VlanPlacementChecker.CheckPrinterPlacement()`, called from `ConfigAuditEngine`

## Performance Audit

New audit section focused on network performance issues (distinct from security audit).

### Port Link Speed Analysis
- Crawl the entire network topology and identify port link speeds that don't make sense
- Reuse the logic from Speed Test network path tracing
- Examples of issues to detect:
  - 1 Gbps uplink on a switch with 2.5/10 Gbps devices behind it
  - Mismatched duplex settings
  - Ports negotiated below their capability (e.g., 100 Mbps on a Gbps port)
  - Bottleneck chains where downstream capacity exceeds upstream link
- Display as performance findings with recommendations

### Jumbo Frames Suggestion
- Suggest enabling Jumbo Frames as a global switching setting when high-speed devices are present
- Trigger: 2+ devices connected at 5 GbE or 10 GbE on access ports (not infrastructure uplinks)
- Rationale: Jumbo frames (9000 MTU) reduce CPU overhead and improve throughput for high-speed transfers
- Implementation:
  - Scan port_table for ports with speed >= 5000 Mbps
  - Exclude infrastructure ports (uplinks, trunks between switches)
  - If count >= 2, check if Jumbo Frames is already enabled globally
  - If not enabled, suggest enabling with explanation of benefits
- Caveats to mention in recommendation:
  - All devices in the path must support jumbo frames
  - Some IoT devices may not support non-standard MTU
  - WAN traffic still uses standard 1500 MTU
- Severity: Informational (performance optimization, not a problem)

### MTU Mismatch Detection
- Detect MTU mismatches along network paths that cause fragmentation or packet drops
- Implementation:
  - During path tracing, SSH into each hop (gateway, switches) to query interface MTU
  - Gateway: `ip link show <interface>` or parse `/sys/class/net/<iface>/mtu`
  - Switches: Check port MTU via SSH (UniFi switches support shell access)
  - Compare MTU values across the path - all devices should match
- Issues to detect:
  - Standard MTU (1500) mixed with Jumbo Frames (9000) in same path
  - Intermediate device with lower MTU than endpoints (causes fragmentation)
  - Jumbo Frames enabled on LAN but not on inter-switch uplinks
  - VPN/tunnel overhead not accounted for (e.g., WireGuard needs ~1420 MTU)
- Display: Show MTU at each hop in path analysis, flag mismatches
- Severity: Warning (mismatches cause performance degradation or silent drops)
- Prerequisite: Reuse SSH infrastructure from SQM/gateway speed tests

### WiFi Optimizer Enhancements
- **Power & Coverage: per-band signal classification** - `GetSignalClass` and `GetSignalBucketClass` in PowerCoverageAnalysis.razor hardcode `RadioBand.Band5GHz` because they operate on aggregate values (avg signal, dBm bucket ranges) without per-client band context. Could classify each client by their actual band first, then aggregate the results. The signal distribution bar chart would need to either split by band or color each client's contribution by their band. Current behavior matches pre-band-aware thresholds so no regression, just a missed opportunity.
- **MLO per-AP detection:** Check MLO status per-AP based on which SSIDs each AP broadcasts (via vap_table), not just global WLAN config. An AP only has MLO impact if it broadcasts an MLO-enabled SSID.

### AP Catalog: Enforce 5 GHz EIRP Cap (US Regulatory)
- FCC caps EIRP at 36 dBm for 5 GHz non-DFS (UNII-3, ch 149-165) and 30 dBm for UNII-1 (ch 36-48)
- The TX Power by Access Point section currently shows uncapped EIRP (TX + gain), which can exceed 36 dBm for high-gain models, implying there's TX power headroom when there isn't
- Already handled for some models on 6 GHz (E7-Campus, E7-Audience have EIRP-aware TX caps in catalog)
- **Affected 5 GHz models (TX + gain > 36):**
  - U7-Outdoor directional: 26 + 13 = 39 (cap TX to 23)
  - U7-Pro-Outdoor directional: 26 + 11 = 37 (cap TX to 25)
  - E7-Campus: 30 + 12 = 42 (cap TX to 24)
  - E7-Audience narrow: 30 + 15 = 45 (cap TX to 21)
  - E7-Audience wide: 30 + 11 = 41 (cap TX to 25)
  - UWB-XG narrow: 25 + 15 = 40 (cap TX to 21)
- **Options:**
  1. Cap MaxTxPowerDbm in the catalog so TX + gain <= 36 for all 5 GHz entries (like we do for 6 GHz on E7 models)
  2. Add regulatory-domain-aware EIRP capping in the display/calculation layer (more complex, handles UNII-1 vs UNII-3 differently)
  3. Show "regulatory max EIRP" alongside "hardware max EIRP" in the UI
- Option 1 is simplest and matches the existing 6 GHz pattern. Option 2 is more accurate but needs channel-to-sub-band mapping.
- **Note:** DFS channels (UNII-2/2C) have lower limits but are dynamic - firmware handles those

### Floor Plan Heatmap - Per-Channel Frequency
- Current heatmap uses a single center frequency per band (2437, 5500, 6500 MHz)
- 5 GHz spans 5150-5850 MHz (channels 36-165), ~1 dB FSPL difference at the extremes
- Material attenuation also varies across the band range
- Implementation:
  - Add `Channel` (or `FrequencyMhz`) to `PropagationAp` from UniFi radio config
  - Map channel number to center frequency (e.g., ch 36 = 5180, ch 149 = 5745)
  - Pass actual frequency to `ComputeSignalAtPoint` instead of band center
  - Update `MaterialAttenuation` to interpolate between band values if needed

### Floor Plan Heatmap - Channel Bandwidth & Per-Client Signal Modeling
- Current heatmap shows raw RSSI (dBm) with no awareness of channel bandwidth
- Wider channels raise the thermal noise floor, reducing effective SNR and usable range:
  - 20 MHz: -96 dBm noise floor, 40 MHz: -93, 80 MHz: -90, 160 MHz: -87, 320 MHz: -84
  - (assumes ~5 dB receiver noise figure)
- A -80 dBm signal gives 16 dB SNR on 20 MHz (decent) but only 7 dB on 160 MHz (unusable)
- Noise floor formula: -174 + 10*log10(BW_Hz) + NF_dB

#### Per-Client Channel Width Negotiation (critical nuance)
- 802.11 negotiates channel width per-client based on capabilities. The AP does NOT force a
  single channel width on all clients. A 160 MHz AP transmits to an 80 MHz client using 80 MHz.
- From the client's perspective, the noise floor matches ITS supported width, not the AP's config:
  - Client supports 80 MHz on a 160 MHz AP -> client sees -90 dBm noise floor, not -87 dBm
  - Client supports 40 MHz -> sees -93 dBm noise floor regardless of AP config
- The client's receiver only processes its supported bandwidth. The extra spectrum the AP has
  configured is simply unused for that client's transmissions.
- This means UniFi Design Center's heatmap (and our current one) shows worst-case coverage for
  clients negotiating the FULL configured width - which are typically the newest devices sitting
  close to the AP where it doesn't matter anyway. The heatmap makes it look like coverage is
  bricked when most clients actually have much better coverage than shown.
- Real-world: most clients are 80 MHz capable. Configuring 160 MHz gives 80 MHz coverage
  footprint for those devices plus throughput bonus for 160 MHz clients when close enough.
- Downsides of wider AP config: consumes more spectrum (matters for multi-AP channel planning),
  and DFS events on the secondary 80 MHz segment can force the whole channel to shift,
  briefly disrupting all clients including 80 MHz ones.

#### Implementation
- Add `ChannelWidthMhz` to `PropagationAp` (pull from UniFi radio config)
- **Default view**: show coverage based on the AP's configured channel width (current behavior
  plus bandwidth-aware color thresholds) - this is the conservative/worst-case view
- **Per-capability tier view**: let users toggle between client capability tiers to see what
  coverage actually looks like for their devices:
  - "160 MHz clients" (worst case, smallest coverage)
  - "80 MHz clients" (most common, realistic coverage)
  - "40 MHz clients" (older devices, best coverage)
  - "20 MHz clients" (legacy, maximum coverage)
  The selected tier overrides the AP's configured width for noise floor and color threshold
  calculations. Signal strength (RSSI) stays the same - only SNR interpretation changes.
- Alternatively/additionally, offer an SNR view mode that shows signal quality (dB above noise
  floor) rather than raw power (dBm), making bandwidth impact visually obvious
- Consider showing a summary callout: "Most of your clients support 80 MHz - here's what they
  actually experience" to educate users about the per-client negotiation reality

#### Implemented Features (v1.x)
The following were implemented in the WiFi Optimizer feature:
- ✅ Channel utilization analysis per AP (Airtime Fairness tab)
- ✅ Client distribution balance across APs (AP Load Balance tab)
- ✅ Signal strength / SNR reporting per client (multiple components)
- ✅ Interference detection - co-channel, adjacent channel (Spectrum Analysis tab)
- ✅ Band steering effectiveness analysis (Band Steering tab)
- ✅ Roaming topology visualization (Connectivity Flow tab)
- ✅ Airtime fairness issues - legacy client impact (Airtime Fairness tab)
- ✅ Site health score with dimensional breakdown
- ✅ Power/coverage analysis with TX power recommendations

## SQM (Smart Queue Management)

### Retrofit Custom Cloudflare Speed Test Binary into Adaptive SQM
- Replace current WAN speed test approach in Adaptive SQM with the custom Cloudflare speed test binary
- The Cloudflare speed test provides more accurate and consistent WAN throughput measurements
- Integration points: SQM calibration, periodic re-calibration, manual speed test triggers
- Should use the same binary/approach as the standalone Cloudflare speed test projects

### Multi-WAN Support
- Support for 3rd, 4th, and N number of WAN connections
- Currently limited to two WAN connections
- Should dynamically detect and configure all available WAN interfaces

### GRE Tunnel Support (Cellular WAN)
- Support GRE tunnel connections from cellular modems (U5G-Max, U-LTE)
- These create GRE tunnels that should be treated as valid WAN interfaces for SQM
- ✅ ~~PPPoE support~~ (done - uses physical interface for lookup, tunnel interface for SQM)

## Multi-Tenant / Multi-Site Support

### Multi-Tenant Architecture
- Add multi-tenant support for single deployment serving multiple sites
- Current architecture: Local console access with local UniFi API
- Target architecture: Support tunneled access to multiple UniFi sites from one deployment
- Deployment models:
  - **Local (default):** Deploy instance at each site for direct LAN API access
  - **Centralized (optional):** Single deployment with VPN/tunnel access to multiple client networks
    - Requires unique IP structure per client (no overlapping subnets)
    - Relies on same local API access, just over tunnel instead of local LAN
- Use cases: MSPs managing multiple customer sites, enterprises with distributed locations
- Considerations:
  - Site/tenant isolation for data and configuration
  - Per-site authentication and API credentials
  - Tenant-aware database schema or separate databases per tenant
  - Site selector/switcher in UI
  - Aggregate dashboard views across sites (optional)

### Federated Authentication & Identity
- External IdP integration for enterprise/MSP deployments
- Protocol support:
  - **SAML 2.0:** Enterprise SSO (Okta, Azure AD, ADFS, etc.)
  - **OIDC/OAuth 2.0:** Modern identity providers (Auth0, Keycloak, Google Workspace)
- Architectural preparation for RBAC (Role-Based Access Control):
  - Abstract authentication layer to support pluggable identity sources
  - Claims/roles mapping from IdP to local permissions
  - Future: Granular permissions per site/tenant (view-only, operator, admin)
- **Token model upgrade** (prerequisite for multi-user):
  - Move from current single JWT to proper access_token + refresh_token OIDC model
  - Short-lived access tokens (1 hour) with long-lived refresh tokens
  - Applies to local auth as well, not just external IdP
  - Token rotation and revocation support
  - Secure refresh token storage (DB-backed with family tracking)
- Considerations:
  - SP-initiated vs IdP-initiated login flows
  - Just-in-time (JIT) user provisioning from IdP claims
  - Session management and token refresh across federated sessions
  - Fallback local auth for break-glass scenarios

## Distribution

### ISO/OVA Image for MSP Deployment
- Create distributable ISO and/or OVA image for MSP users
- Pre-configured Linux appliance with Network Optimizer installed
- Easy deployment to customer sites without Docker expertise
- Consider: Ubuntu Server base, auto-updates, web-based initial setup

## General

### Refactor Program.cs - Extract Business Logic and Break Up API Sets
- **Issue:** `Program.cs` has grown into a monolith with schedule executor implementations, API endpoint registrations, and business logic all inline
- **Goal:** Clean separation of concerns:
  - Extract schedule executor registrations into a dedicated class (e.g., `ScheduleExecutorSetup.cs`)
  - Break API endpoints into logical groups using minimal API route groups or extension methods (e.g., `SpeedTestEndpoints.cs`, `AuditEndpoints.cs`, `ThreatEndpoints.cs`)
  - Move inline business logic out of endpoint handlers into services
- **Priority:** Medium - not blocking but makes maintenance harder as the app grows

### Refactor DnsSecurityAnalyzer.AnalyzeAsync() Parameter Hell
- **Issue:** `DnsSecurityAnalyzer.AnalyzeAsync()` now takes 12 parameters (was 7, grew during DNAT/firewall groups/URL work):
  ```csharp
  public async Task<DnsSecurityResult> AnalyzeAsync(
      JsonElement? settingsData, List<FirewallRule>? firewallRules,
      List<SwitchInfo>? switches, List<NetworkInfo>? networks,
      JsonElement? deviceData, int? customDnsManagementPort,
      JsonElement? natRulesData, List<int>? dnatExcludedVlanIds,
      string? externalZoneId, FirewallZoneLookup? zoneLookup,
      Dictionary<string, UniFiFirewallGroup>? firewallGroups,
      string? customDnsManagementUrl)
  ```
  Plus 5 convenience overloads that chain to it.
- **Problems:**
  - Easy to pass arguments in wrong order (all are nullable)
  - Tests are verbose with many `null` placeholders
  - Adding new parameters requires updating all call sites and overloads
  - The overload chain (lines 47-77) is getting unwieldy
- **Proposed fix:** Create `DnsAnalysisRequest` record/class:
  ```csharp
  public record DnsAnalysisRequest
  {
      public JsonElement? SettingsData { get; init; }
      public List<FirewallRule>? FirewallRules { get; init; }
      public List<SwitchInfo>? Switches { get; init; }
      public List<NetworkInfo>? Networks { get; init; }
      public JsonElement? DeviceData { get; init; }
      public int? CustomDnsManagementPort { get; init; }
      public string? CustomDnsManagementUrl { get; init; }
      public JsonElement? NatRulesData { get; init; }
      public List<int>? DnatExcludedVlanIds { get; init; }
      public string? ExternalZoneId { get; init; }
      public FirewallZoneLookup? ZoneLookup { get; init; }
      public Dictionary<string, UniFiFirewallGroup>? FirewallGroups { get; init; }
  }
  ```
- **Benefits:**
  - Named parameters make call sites self-documenting
  - Adding new fields doesn't break existing callers
  - Eliminates the 5 overloads - just one method with a request object
  - Test setup becomes clearer
- **Also applies to:** Other analyzers with similar parameter patterns

### Consolidate DNAT Rule Coverage Type Strings
- **Issue:** `DnatRuleInfo.CoverageType` uses magic strings: `"network"`, `"subnet"`, `"single_ip"`, `"inverted_address"`, `"interface"`
- **Current usage:** Set in `ParseSourceFilter()`, consumed in `Analyze()` switch statement
- **Fix:** Replace with an enum `DnatCoverageType` for type safety and discoverability
- **Scope:** `DnatDnsAnalyzer.cs` only - fully self-contained

### ThirdPartyDnsDetector Probe Method Duplication
- **Issue:** Two overloads of `TryProbePiholeEndpointAsync` and `TryProbeAdGuardHomeEndpointAsync` - one takes a full URL, one takes IP+port+scheme. The logic is nearly identical.
- **Fix:** Unify into a single method that takes a URL string. The IP+port caller can construct the URL before calling.
- **Scope:** `ThirdPartyDnsDetector.cs` only

### Rename ISpeedTestRepository to IGatewayRepository
- **Issue:** `ISpeedTestRepository` is a misleading name - it handles Gateway SSH settings, iperf3 results, AND SQM WAN configuration
- **Current location:** `src/NetworkOptimizer.Storage/Interfaces/ISpeedTestRepository.cs`
- **Proposed name:** `IGatewayRepository` (all methods are gateway-related)
- **Refactor scope:**
  - Rename interface and implementation (`SpeedTestRepository.cs`)
  - Update all DI registrations in `Program.cs`
  - Update all injection sites across the codebase
  - Consider if gateway SSH settings should be a separate repository

### Database Normalization Review
- Review SQLite schema for proper normal form (1NF, 2NF, 3NF)
- Ensure proper use of primary keys, foreign keys, and indices
- Audit table relationships and consider splitting denormalized data
- JSON columns are intentional for flexible nested data (e.g., PathAnalysisJson, RawJson)
- Consider: Separate Clients table with FK references instead of storing ClientMac/ClientName inline

### Normalize Environment Variable Handling
- Current: Mixed patterns for reading configuration
  - Direct env var reads: `HOST_IP`, `APP_PASSWORD`, `HOST_NAME` (via `Environment.GetEnvironmentVariable()`)
  - .NET configuration: `Iperf3Server:Enabled` (via `IConfiguration`, requires `Iperf3Server__Enabled` env var format)
- Problem: Inconsistent for native deployments (Docker translates `IPERF3_SERVER_ENABLED` → `Iperf3Server__Enabled`)
- Options:
  1. Route everything through .NET configuration (use `__` notation everywhere)
  2. Route everything through direct env var reads (simpler for native)
  3. Support both patterns in app (check env var first, fall back to config)
- Low priority but would improve consistency

### Debounce UI-Triggered Modem Polls
- **Issue:** Multiple rapid modem polls can occur when navigating between pages
- **Cause:** `CellularStatsPanel` triggers `PollModemAsync` on render when no cached stats exist; multiple component instances can poll simultaneously before any completes
- **Observed:** 4-5 polls within 4 seconds when navigating dashboard → settings
- **Fix:** Add debounce or lock around UI-triggered polls in `CellularModemService`
- **Severity:** Low (causes extra SSH traffic but no errors)
- **Partial:** Basic `_isPolling` lock prevents concurrent polls, but no time-based debounce yet

### Shared IP-to-Client-Name Resolver
- Threat Dashboard resolves local IPs to UniFi client names inline (fetches clients, builds IP→name dict)
- Currently cached for 30 seconds (static across Blazor circuits) to avoid hammering the API
- **Note:** Real-time features (e.g., live threat feed, active monitoring) will need to invalidate/refresh the cache before using it, since device IPs can change via DHCP
- Other pages that display IPs could benefit from the same lookup:
  - Security Audit (firewall rules referencing IPs)
  - Config Optimizer (device references)
- Refactor into a shared service (e.g., `IClientNameResolver` in `NetworkOptimizer.Web/Services/`)
- Shared service should expose `InvalidateCache()` for real-time consumers

### Uniform Date/Time Formatting in UI
- Audit all date/time displays across the UI for consistency
- Standardize format (e.g., "Jan 4, 2026 3:45 PM" vs "2026-01-04 15:45:00")
- Consider user timezone preferences
- Affected areas: Speed test results, audit history, device last seen, logs

## UniFi Device Classification (v2 API)

The UniFi v2 device API (`/proxy/network/v2/api/site/{site}/device`) returns multiple device arrays for improved device classification and VLAN security auditing.

### Device Arrays from v2 API

| Array | Description | VLAN Recommendation | Status |
|-------|-------------|---------------------|--------|
| `network_devices` | APs, Switches, Gateways | Management VLAN | Existing |
| `protect_devices` | Cameras, Doorbells, NVRs, Sensors | Security VLAN | Done |
| `access_devices` | Door locks, readers | Security VLAN | TODO |
| `connect_devices` | EV chargers, other Connect devices | IoT VLAN | TODO |
| `talk_devices` | Intercoms, phones | IoT/VoIP VLAN | TODO |
| `led_devices` | LED controllers, lighting | IoT VLAN | TODO |

### Protect Infrastructure Devices (SuperLink, Sensors, Chimes)
- Currently excluded from VLAN placement checks: SuperLink Hub, Sensors, Chimes, Bridges
- These are wired (SuperLink) or wireless Protect devices that aren't cameras/doorbells/NVRs
- VLAN placement is ambiguous - depends on user's network design:
  - If Protect Console is on Security VLAN, these should follow
  - If Protect Console is on Management VLAN, SuperLink could go either way
  - Sensors and chimes carry security-sensitive data (motion, door open/close) - some users consider this Security VLAN worthy, others treat them as IoT
- Current `RequiresSecurityVlan` only covers the unambiguous set: cameras, doorbells, NVRs, AI Key
- Options:
  1. Add these to `RequiresSecurityVlan` and always recommend Security VLAN
  2. Tie recommendation to where the Protect Console itself lives (if Console is on Security, recommend Security for all Protect devices)
  3. Leave it to the Manual Network Purpose Override feature (let users decide)
- Likely best approach: option 2 (follow the Console) with option 3 as fallback

### Phase 2: Access Devices (Door Access)
- [ ] Parse `access_devices` array
- [ ] Identify door locks, card readers, intercoms
- [ ] Map to `ClientDeviceCategory.SmartLock` or new `AccessControl` category
- [ ] Recommend Security VLAN placement

### Phase 3: Connect Devices (EV Chargers, etc.)
- [ ] Parse `connect_devices` array
- [ ] Identify EV chargers, power devices
- [ ] Map to `ClientDeviceCategory.SmartPlug` or new `EVCharger` category
- [ ] Recommend IoT VLAN placement

### Phase 4: Talk Devices (Intercoms/Phones)
- [ ] Parse `talk_devices` array
- [ ] Identify intercoms, VoIP phones
- [ ] Map to `ClientDeviceCategory.VoIP` or `SmartSpeaker`
- [ ] Consider VoIP VLAN vs IoT VLAN recommendation

### Phase 5: LED Devices
- [ ] Parse `led_devices` array
- [ ] Identify LED controllers, smart lighting
- [ ] Map to `ClientDeviceCategory.SmartLighting`
- [ ] Recommend IoT VLAN placement

**Note:** The v2 API is only available on UniFi OS controllers (UDM, UCG, etc.). Device classification from the controller API is 100% confidence since the controller knows its own devices.

## Standalone Controller Support

### API Path Differences
Currently only tested with UniFi OS controllers (UDM, Cloud Gateway). Standalone controllers use different API paths:

| Controller Type | API Path Pattern |
|-----------------|------------------|
| UniFi OS (UDM/UCG) | `https://<ip>/proxy/network/api/s/{site}/stat/sta` |
| Standalone Controller | `https://<ip>/api/s/{site}/stat/sta` |

The app auto-detects controller type via login response, but needs testing with standalone controllers to verify:
- Path detection logic in `UniFiApiClient`
- All API endpoints work correctly
- Authentication flow differences (if any)


================================================
FILE: docker/.dockerignore
================================================
# Git
.git/
.gitignore
.gitattributes

# Docker
docker/
Dockerfile
docker-compose.yml
.dockerignore

# Documentation
*.md
docs/
*.pdf

# IDE
.vs/
.vscode/
.idea/
*.suo
*.user
*.userosscache
*.sln.docstates

# Build artifacts
**/bin/
**/obj/
**/out/

# Test coverage
**/TestResults/
**/*.coverage
**/*.coveragexml

# NuGet (exclude default caches, but keep our local packages source)
!packages/
!packages/*.nupkg

# Node modules (if any)
node_modules/
npm-debug.log

# OS files
.DS_Store
Thumbs.db
*.swp
*.swo
*~

# Logs
*.log
logs/

# Data directories
data/
ssh-keys/

# Environment files
.env
.env.local
.env.production

# Temporary files
tmp/
temp/
*.tmp


================================================
FILE: docker/.env.example
================================================
# Network Optimizer Environment Configuration
# Copy this file to .env and update with your values

# ===== Network Binding =====
# BIND_LOCALHOST_ONLY: Controls which network interfaces the app listens on
#   - false (default): Binds to 0.0.0.0:8042 (accessible from network)
#   - true: Binds to 127.0.0.1:8042 (localhost only, use with reverse proxy on same host)
# BIND_LOCALHOST_ONLY=false

# ===== Timezone Configuration =====
# Common US timezones:
#   America/New_York (Eastern), America/Chicago (Central),
#   America/Denver (Mountain), America/Los_Angeles (Pacific)
# Other examples:
#   Europe/London, Europe/Paris, Asia/Tokyo, Australia/Sydney
TZ=America/New_York

# ===== Application Password =====
# Password precedence: Database (Settings UI) > APP_PASSWORD env var > Auto-generated
#
# On first run, an auto-generated password is shown in the logs.
# You can then set a permanent password in Settings > Admin Password (recommended).
# APP_PASSWORD is a fallback - useful for Docker deployments where you want
# to set the password before the first login.
# APP_PASSWORD=your_secure_password

# ===== Host Identity & Canonical URL Enforcement =====
# These settings identify the server for speed testing and optionally enforce a canonical URL.
# Redirects (302) only occur when HOST_NAME or REVERSE_PROXIED_HOST_NAME is set.
# HOST_IP alone does NOT trigger redirects (allows access via any hostname).

# HOST_IP: Server's IP address
#   - Used for: Speed test path analysis, CORS, OpenSpeedTest URL in UI
#   - Required for: Path analysis when server IP can't be auto-detected (bridge networking)
#   - Note: Does NOT enforce redirects (users can still access via hostname)
# HOST_IP=192.168.1.100

# HOST_NAME: Server's hostname (recommended for better UX)
#   - Used for: Canonical URL enforcement, user-facing URLs, OpenSpeedTest link in UI
#   - Requires: DNS resolution by clients (can be local DNS via router/Pi-hole)
#   - Examples: nas, server.local, optimizer.home.arpa
# HOST_NAME=nas

# REVERSE_PROXIED_HOST_NAME: Hostname when behind a reverse proxy
#   - Used for: Canonical URL (https, no port), API URL for OpenSpeedTest result reporting
#   - Set to: The hostname your reverse proxy serves (internal or public)
#   - Note: OpenSpeedTest container is still accessed via HOST_NAME/HOST_IP:3005
# REVERSE_PROXIED_HOST_NAME=optimizer.example.com

# ===== Client Speed Testing =====
# Browser-based: Speed Test runs on port 3005 (configurable), results auto-reported if HOST_IP/HOST_NAME set
#   To disable: comment out the network-optimizer-speedtest service in docker-compose.yml
# CLI-based: Enable iperf3 server mode for testing from devices with iperf3 installed

# Enable iperf3 server (listens on port 5201)
# IPERF3_SERVER_ENABLED=true

# OpenSpeedTest port (default 3005)
#   - Used for direct access without a reverse proxy (e.g., http://server:3005)
#   - When behind a reverse proxy, clients use HTTP_PORT/HTTPS_PORT below instead
#   - Change if: Port 3005 conflicts with another service
# OPENSPEEDTEST_PORT=3005

# OpenSpeedTest hostname (defaults to HOST_NAME)
#   - Set if speedtest is accessed via a different hostname than the main app
#   - Example: speedtest.example.com when main app is at optimizer.example.com
# OPENSPEEDTEST_HOST=speedtest.example.com

# OpenSpeedTest HTTPS mode (default false)
#   Set to "true" when the speed test is behind a TLS-terminating reverse proxy.
#   UI links will use https:// and CORS will include the HTTPS origin.
# OPENSPEEDTEST_HTTPS=true
#
# HTTPS proxy port (default 443)
#   Change if your TLS proxy listens on a non-standard HTTPS port
# OPENSPEEDTEST_HTTPS_PORT=443
#
# IMPORTANT: Speedtest reverse proxies MUST force HTTP/1.1 for accurate results.
# HTTP/2+ multiplexing inflates speeds. However, HTTP/1.1 will BREAK the main
# Network Optimizer app (Blazor requires HTTP/2+ for WebSockets).
#
# If you use a TLS proxy, you need TWO separate hostnames:
#   - speedtest.example.com → HTTP/1.1 → localhost:3005 (speedtest)
#   - optimizer.example.com → HTTP/2+  → localhost:8042 (main app)
#
# See NetworkOptimizer-Proxy for ready-made Traefik configs that handle this.

# ===== Advanced Settings =====
# Log levels: Trace, Debug, Information, Warning, Error, Critical
# LOG_LEVEL=Information       # General (framework, EF Core, etc.)
# APP_LOG_LEVEL=Debug         # Network Optimizer application


================================================
FILE: docker/DEPLOYMENT.md
================================================
# Deployment Guide

Production deployment guide for Network Optimizer.

## Deployment Options

| Option | Best For | Guide |
|--------|----------|-------|
| Linux + Docker | Self-built servers, VMs, cloud (recommended) | [Below](#1-linux--docker-recommended) |
| Proxmox LXC | Homelab virtualization, one-liner install | [Proxmox Guide](#2-proxmox-lxc) |
| NAS + Docker | Synology, QNAP, Unraid | [NAS Deployment](#3-nas-deployment-docker) |
| Home Assistant | Add-ons | [Home Assistant](#5-home-assistant) |
| Windows Installer | Windows desktops/servers | [Download from Releases](https://github.com/Ozark-Connect/NetworkOptimizer/releases) |
| macOS Native | Mac servers, multi-gigabit speed testing | [macOS Installation](../docs/MACOS-INSTALLATION.md) |
| Linux Native | Maximum performance, no Docker | [Native Guide](NATIVE-DEPLOYMENT.md#linux-deployment) |

---

### 1. Linux + Docker (Recommended)

Deploy on any Linux server using Docker Compose. This is the recommended approach for self-built NAS, home servers, VMs, and cloud instances.

**Requirements:**
- Docker 20.10+ and Docker Compose 2.0+
- 2GB RAM minimum (4GB recommended)
- 10GB disk space
- Ubuntu 20.04+, Debian 11+, RHEL/CentOS 8+, or compatible

#### Quick Start

```bash
# Install Docker (if not already installed)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group changes
```

> **Choose a stable location:** Deploy to a permanent directory like `/opt/network-optimizer`. Avoid home directories or `/tmp` which may cause issues with permissions, cleanup, or migrations.

**Option A: Pull Docker Image (Recommended)**

```bash
# Create directory in /opt (recommended)
sudo mkdir -p /opt/network-optimizer && sudo chown $USER: /opt/network-optimizer
cd /opt/network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
nano .env  # Set timezone and other options (optional)
docker compose up -d
```

**Option B: Build from Source**

```bash
cd /opt  # or your preferred stable location
sudo git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
sudo chown -R $USER: NetworkOptimizer
cd NetworkOptimizer/docker
cp .env.example .env
nano .env  # Set timezone and other options (optional)
docker compose build
docker compose up -d
```

**Verify Installation:**

```bash
# Check logs for the auto-generated admin password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"

# Verify health
docker compose ps
curl http://localhost:8042/api/health
```

Access at: **http://your-server:8042**

#### Network Mode Options

**Host Networking (Recommended for Linux):**
```yaml
# docker-compose.yml uses network_mode: host by default
# This provides best performance and accurate IP detection
```

**Bridge Networking (if host mode unavailable):**
```bash
# Use docker-compose.macos.yml which uses port mapping
# IMPORTANT: Set HOST_IP in .env to your server's IP for accurate path analysis
docker compose -f docker-compose.macos.yml up -d
```

#### Service Management

```bash
# View logs
docker compose logs -f

# Restart
docker compose restart

# Stop
docker compose down

# Update to latest
docker compose pull
docker compose up -d

# Full rebuild (after Dockerfile changes)
docker compose build --no-cache
docker compose up -d
```

#### Systemd Integration (Auto-Start on Boot)

```bash
# Enable Docker to start on boot
sudo systemctl enable docker

# Docker Compose containers with restart: unless-stopped will auto-start
```

Or create a dedicated systemd service:

```bash
sudo cat > /etc/systemd/system/network-optimizer.service << 'EOF'
[Unit]
Description=Network Optimizer
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/network-optimizer/docker
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable network-optimizer
```

---

### 2. Proxmox LXC

The easiest way to deploy on Proxmox. Run this one-liner on your **Proxmox VE host**:

```bash
bash -c "$(wget -qLO - https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/proxmox/install.sh)"
```

The interactive script will:
1. Create a privileged Debian LXC container
2. Install Docker and Docker Compose
3. Deploy Network Optimizer with Docker Compose
4. Optionally deploy a [Traefik HTTPS proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy) with automatic Let's Encrypt certificates (requires Cloudflare DNS)
5. Configure auto-start on boot

**Requirements:**
- Proxmox VE 7.0 or later
- 10GB disk space, 2GB RAM minimum
- Internet access for downloading images

**After Installation:**
```bash
# Get the auto-generated admin password
pct exec <CT_ID> -- docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"

# Access the web UI
http://<container-ip>:8042
```

For advanced configuration, troubleshooting, and manual installation see the [full Proxmox guide](../scripts/proxmox/README.md).

---

### 3. NAS Deployment (Docker)

For commercial NAS devices with container support.

#### Synology NAS

1. Install Container Manager from Package Center
2. Clone or upload the repository to `/docker/network-optimizer`
3. Copy `.env.example` to `.env` and configure
4. Create project in Container Manager pointing to docker-compose.yml
5. Start containers

**Note:** If using bridge networking, set `HOST_IP` in `.env` to your NAS IP address.

#### QNAP NAS

1. Install Container Station
2. Create shared folders
3. Import `docker-compose.yml`
4. Configure environment variables
5. Deploy stack

#### Unraid

1. Install Community Applications plugin
2. Search for "Network Optimizer"
3. Deploy both network-optimizer and network-optimizer-speedtest containers

Community templates maintained by [@stefan-matic](https://github.com/stefan-matic/unraid-templates).

---
Or use manual Docker Compose deployment (note: cannot be managed by Unraid GUI if deployed via compose)

### 4. Native Deployment (No Docker)

For maximum network performance or systems without Docker, run natively on the host.

**Best for:**
- macOS systems (avoids Docker Desktop's ~1.8 Gbps network throughput limitation)
- Systems where Docker overhead is undesirable
- Dedicated appliances

**Supported Platforms:**
- macOS 11+ (Intel or Apple Silicon)
- Linux (Ubuntu 20.04+, Debian 11+, RHEL 8+)
- Windows: Use the [Windows Installer](https://github.com/Ozark-Connect/NetworkOptimizer/releases) instead

See [Native Deployment Guide](NATIVE-DEPLOYMENT.md) for macOS and Linux instructions.

---

### 5. Home Assistant

Network Optimizer can be installed as two Home Assistant add-ons. See [issue #201](https://github.com/Ozark-Connect/NetworkOptimizer/issues/201) for setup instructions and discussion.

For the initial admin password, check the add-on's **Log** tab instead of using the `docker logs` command.

## Pre-Deployment Checklist

- [ ] Docker and Docker Compose installed
- [ ] Sufficient disk space (10GB minimum)
- [ ] Network access to UniFi Controller
- [ ] Firewall rules configured (if applicable)
- [ ] `.env` file configured with secure passwords
- [ ] SSL certificates ready (if using HTTPS)
- [ ] SSH enabled on UniFi devices (required for SQM and LAN speed testing, see below)

## Installation Steps (NAS)

These detailed steps are for NAS deployment. For other deployment options, see the guides above.

> **Note:** If `docker compose` doesn't work on older NAS firmware, try `docker-compose` (hyphenated).

> **Choose a stable location:** Deploy to a permanent directory like `/volume1/docker/network-optimizer` (Synology) or equivalent. Avoid temporary locations that may be cleaned up or have permission issues.

### 1. Download Files

**Option A: Pull Docker Image (Recommended)**

```bash
mkdir network-optimizer && cd network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
```

**Option B: Build from Source**

```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
```

### 2. Configure Environment

```bash
# Copy template
cp .env.example .env

# Edit with your settings
nano .env
```

**Recommended changes:**
```env
# Set your timezone
TZ=America/Chicago
```

**Admin Password:**

On first run, an auto-generated password is displayed in the logs. After logging in,
go to **Settings > Admin Password** to set your own password (recommended).

Password precedence: Database (Settings UI) > `APP_PASSWORD` env var > Auto-generated

Optionally, set `APP_PASSWORD` in `.env` if you want to configure a password before first login.

### 3. Deploy Stack

```bash
docker compose up -d
```

### 4. Verify Deployment

```bash
# Check service health
docker compose ps

# View logs
docker compose logs -f

# Test health endpoint
curl http://localhost:8042/api/health
```

Expected output:
```
NAME                          STATUS
network-optimizer             Up (healthy)
```

### 5. Access Web UI

- Web UI: http://your-server:8042

## Production Configuration

### HTTPS with Reverse Proxy

Use nginx, Caddy, or Traefik for SSL termination.

**If the reverse proxy is on the same host**, add to your `.env`:
```env
BIND_LOCALHOST_ONLY=true
```
This binds the app to `127.0.0.1:8042` instead of all interfaces, so only the local proxy can access it.

#### Traefik (Recommended for Speed Testing)

If you use the browser-based speed test (OpenSpeedTest), Traefik is the recommended reverse proxy. Most proxies negotiate HTTP/2 at the TLS level, and HTTP/2 multiplexing interferes with speed test throughput measurements. Traefik's per-router TLS options let you force HTTP/1.1 for the speed test hostname while keeping HTTP/2 for the main app - all on one port 443.

See [NetworkOptimizer-Proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy) for a ready-to-use Docker Compose setup with automatic Let's Encrypt certificates via Cloudflare DNS-01.

**Proxmox users:** The [Proxmox LXC installer](../scripts/proxmox/README.md) can set up Traefik automatically during installation.

**Windows users:** Traefik is available as an optional feature in the MSI installer.

#### Nginx Example

```nginx
# /etc/nginx/sites-available/network-optimizer
server {
    listen 80;
    server_name network-optimizer.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name network-optimizer.example.com;

    ssl_certificate /etc/letsencrypt/live/network-optimizer.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/network-optimizer.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Blazor Web UI
    location / {
        proxy_pass http://localhost:8042;
        proxy_http_version 1.1;

        # WebSocket support for Blazor
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouts for long-running operations
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}
```

Enable and restart:
```bash
sudo ln -s /etc/nginx/sites-available/network-optimizer /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```

#### Caddy Example (Automatic HTTPS)

```caddy
# /etc/caddy/Caddyfile
network-optimizer.example.com {
    reverse_proxy localhost:8042
}
```

Restart Caddy:
```bash
sudo systemctl reload caddy
```

### Firewall Configuration

#### UFW (Ubuntu/Debian)

```bash
# Allow SSH
sudo ufw allow 22/tcp

# Allow HTTP/HTTPS (if using reverse proxy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Or allow direct access to the web UI
sudo ufw allow 8042/tcp  # Web UI

sudo ufw enable
```

#### firewalld (RHEL/CentOS)

```bash
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=8042/tcp
sudo firewall-cmd --reload
```

### Backup Strategy

#### Automated Backups

Create backup script:
```bash
#!/bin/bash
# /usr/local/bin/backup-network-optimizer.sh

BACKUP_DIR=/backups/network-optimizer
DATE=$(date +%Y%m%d-%H%M%S)

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup SQLite data and configuration
tar czf $BACKUP_DIR/data-$DATE.tar.gz -C /path/to/docker data/

# Cleanup old backups (keep last 7 days)
find $BACKUP_DIR -type f -mtime +7 -delete

echo "Backup completed: $DATE"
```

Add to crontab:
```bash
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-network-optimizer.sh >> /var/log/network-optimizer-backup.log 2>&1
```

#### Restore from Backup

```bash
# Stop services
docker compose down

# Restore data
tar xzf /backups/network-optimizer/data-20240101-020000.tar.gz -C /path/to/docker/

# Start services
docker compose up -d
```

### Monitoring and Alerting

#### System Monitoring

Use Docker healthchecks:
```bash
# Check all services
watch docker compose ps

# Monitor resource usage
docker stats
```

#### Log Monitoring

Centralized logging with rsyslog or similar:
```yaml
# docker-compose.yml addition
logging:
  driver: syslog
  options:
    syslog-address: "udp://your-syslog-server:514"
    tag: "network-optimizer"
```

#### Uptime Monitoring

Use external monitoring:
- UptimeRobot
- Healthchecks.io
- Self-hosted Uptime Kuma

Configure health check endpoint:
```bash
# Monitor this endpoint
http://your-server:8042/api/health
```

### Resource Limits

Add resource constraints for production:

```yaml
# docker-compose.override.yml
services:
  network-optimizer:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '1.0'
          memory: 1G
    restart: always
```

Apply with:
```bash
docker compose up -d
```

### Logging Configuration

Control log verbosity via environment variables in `.env`:

```env
# General framework logging (Microsoft, EF Core, ASP.NET, etc.)
LOG_LEVEL=Information

# Network Optimizer application logging
APP_LOG_LEVEL=Debug
```

**Log Levels (least to most verbose):** Critical, Error, Warning, Information, Debug, Trace

**Common configurations:**

| Scenario | LOG_LEVEL | APP_LOG_LEVEL |
|----------|-----------|---------------|
| Production (default) | Information | Information |
| Debugging app issues | Information | Debug |
| Full diagnostics | Debug | Debug |

After changing `.env`, recreate the container to apply:
```bash
docker compose down && docker compose up -d
```

**Note:** `docker compose restart` does NOT reload environment variables. You must recreate the container.

View logs:
```bash
# Follow logs
docker compose logs -f network-optimizer

# Last 100 lines
docker compose logs --tail=100 network-optimizer
```

#### Windows Service

On Windows, logs are written to `<install-dir>\logs\networkoptimizer-YYYY-MM-DD.log` (rolling daily, 7-day retention).

To change log levels, set environment variables on the Windows service via the registry. This avoids modifying any config files.

**Enable debug logging for Network Optimizer:**

```powershell
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$existing = (Get-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue).Environment
$env = [string[]](@($existing | Where-Object { $_ }) + "Logging__LogLevel__NetworkOptimizer=Debug")
Set-ItemProperty $regPath -Name Environment -Value $env
Restart-Service NetworkOptimizer
```

**Enable debug logging for Traefik (HTTPS certificate issues):**

If HTTPS isn't working after a couple minutes (certificate errors in the browser), enable Traefik debug logging to see why certificate issuance is failing. Traefik runs as a child process and its output is captured into the app log. You need both the Traefik log level (controls what Traefik emits) and the app log level (controls what gets written to the log file):

```powershell
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$existing = (Get-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue).Environment
$env = [string[]](@($existing | Where-Object { $_ }) + "Logging__LogLevel__NetworkOptimizer=Debug")
Set-ItemProperty $regPath -Name Environment -Value $env

# Also set Traefik's own log level to DEBUG (this is separate from the app log level)
Set-ItemProperty -Path "HKLM:\SOFTWARE\Ozark Connect\Network Optimizer" -Name "TRAEFIK_LOG_LEVEL" -Value "DEBUG"

Restart-Service NetworkOptimizer
```

**Remove debug logging when done:**

```powershell
# Remove service environment variables
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$env = [string[]]((Get-ItemProperty $regPath -Name Environment).Environment | Where-Object { $_ -notlike "Logging__*" })
if ($env.Count -gt 0) {
    Set-ItemProperty $regPath -Name Environment -Value $env
} else {
    Remove-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue
}

# Reset Traefik log level
Set-ItemProperty -Path "HKLM:\SOFTWARE\Ozark Connect\Network Optimizer" -Name "TRAEFIK_LOG_LEVEL" -Value "INFO"

Restart-Service NetworkOptimizer
```

**View logs:**

```powershell
# Follow the current log file
Get-Content "<install-dir>\logs\networkoptimizer-*.log" -Tail 50 -Wait
```

## Upgrade Procedure

### Option A: Using Docker Image (Recommended)

If you deployed using the pre-built Docker image:

```bash
cd /path/to/network-optimizer
docker compose pull
docker compose up -d
```

### Option B: Building from Source

If you cloned the repository and build locally:

```bash
cd /path/to/NetworkOptimizer
git fetch origin
git checkout main
git pull
cd docker && docker compose build && docker compose up -d
```

For significant updates (major version changes or Dockerfile modifications), use `--no-cache`:

```bash
docker compose build --no-cache
docker compose up -d
```

### Windows Installer

Download the latest MSI from [GitHub Releases](https://github.com/Ozark-Connect/NetworkOptimizer/releases) and run it. The installer upgrades in-place, preserving your database, settings, and encryption keys. The Network Optimizer service restarts automatically after the upgrade.

### macOS Native

```bash
cd NetworkOptimizer
git pull
./scripts/install-macos-native.sh
```

The install script preserves your database, encryption keys, and `start.sh` configuration by backing them up before reinstalling. See the [macOS Installation Guide](../docs/MACOS-INSTALLATION.md) for details.

### Verify Update

```bash
docker compose ps
docker compose logs -f
```

## Migrating from Build-from-Source to Pre-Built Images

If you've been building from source and want to switch to the pre-built Docker images:

**Why migrate?** Pre-built images are faster to update (no build step), tested before release, and don't require the full git repository.

**Important:** When you build locally, Docker tags your image as `ghcr.io/ozark-connect/network-optimizer:latest`. Simply running `docker compose pull` won't overwrite this because the compose file has a `build:` directive. You need to force the pull and switch to the production compose file.

```bash
cd /opt/network-optimizer  # or wherever you deployed

# Stop running containers
docker compose down

# Force pull registry images (overwrites locally-built images)
docker pull ghcr.io/ozark-connect/network-optimizer:latest
docker pull ghcr.io/ozark-connect/speedtest:latest

# Back up your current compose file (optional)
mv docker-compose.yml docker-compose.yml.build-backup

# Download the production compose file (no build directives)
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml

# Start with pre-built images
docker compose up -d

# Optional: clean up old build cache to free disk space
docker builder prune -f
```

Your `data/`, `logs/`, and `.env` files are preserved. Future updates are now just:
```bash
docker compose pull && docker compose up -d
```

## Troubleshooting

### Reset Admin Password

If you've forgotten your password or need to reset it, use the reset script:

```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash
```

Or download and run it (useful inside Proxmox LXC or restricted environments):

```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh -o reset-password.sh
bash reset-password.sh
```

The script auto-detects your Docker container, clears the password, restarts, and displays the new temporary password.

**Manual fallback** (if you prefer not to use the script):

```bash
# Clear the password from the database
docker exec network-optimizer sqlite3 /app/data/network_optimizer.db \
    "UPDATE AdminSettings SET Password = NULL, Enabled = 0;"

# Restart to trigger auto-generated password
docker restart network-optimizer

# View the new auto-generated password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

### Container Won't Start

```bash
# Check logs for errors
docker compose logs network-optimizer

# Common issues:
# - Port 8042 already in use: stop conflicting service or change port
# - Permission denied on data directory: check ownership of mounted volumes
# - Out of disk space: df -h
```

### Can't Connect to UniFi Controller

1. Verify the controller URL is correct (include https:// and port if non-standard)
2. Ensure you're using a **Local Access Only** account, not Ubiquiti SSO (see [UniFi Account setup](#unifi-account))
3. Check network connectivity: `curl -k https://your-controller:443`
4. For self-signed certificates, enable "Ignore SSL errors" in Settings

### SSH Connection Failures

```bash
# Test SSH manually from the container
docker exec -it network-optimizer ssh username@gateway-ip

# Common issues:
# - SSH not enabled on device (see UniFi SSH Configuration section)
# - Wrong credentials
# - Firewall blocking port 22
# - Host key verification (container may need to accept new host keys)
```

### Blazor UI Not Loading / Disconnects

Blazor Server uses WebSocket connections. If the UI shows "Reconnecting..." or won't load:

1. Check that your reverse proxy supports WebSockets (see nginx/Caddy examples above)
2. Ensure proxy timeouts are sufficient (60s+)
3. Check browser console for connection errors

### Database Issues

The SQLite database is stored in the `data/` volume. If you encounter database errors:

```bash
# Check database file exists and has correct permissions
docker exec network-optimizer ls -la /app/data/

# View recent application logs
docker compose logs --tail=100 network-optimizer
```

## Security Considerations

### Protect Your Credentials

The `.env` file and SQLite database contain sensitive information:

```bash
# Restrict .env file permissions
chmod 600 .env

# Data directory contains the database with stored credentials
chmod 700 data/
```

### Network Access

Network Optimizer stores UniFi controller credentials and SSH passwords. Limit access to the web UI:

- Use a reverse proxy with authentication if exposing beyond your local network
- Consider firewall rules to restrict access to trusted IPs
- Use HTTPS via reverse proxy (see examples above)

### UniFi Account

Network Optimizer supports UniFi OS devices (UDM, UCG, UDR, Cloud Key) and self-hosted UniFi Network Server installations.

Create a dedicated **Local Access Only** account on your UniFi controller for Network Optimizer. Ubiquiti SSO accounts will not work.

**Quick Setup:** Create a Local Access Only account with **Super Admin** role.

**Restricted Setup (recommended):**
1. Open UniFi Network: `https://<gateway-ip>` or `https://unifi.ui.com`
2. Click **Admin & Users** at the bottom of the side menu
3. Click **Create New** → **Create New User**
4. Enter a name and email for this service account
5. Check **Admin** and **Restrict to Local Access Only**
6. Uncheck **Use a Predefined Role** and set:
   - **Network:** View Only
   - **Protect:** View Only
   - **User & Account Management:** None
7. Set a secure password and save

Use this username and password in Network Optimizer Settings.

## Support

- GitHub Issues: https://github.com/Ozark-Connect/NetworkOptimizer/issues
- Email: tj@ozarkconnect.net

## UniFi SSH Configuration

SSH access is required for some features but not others. Here's what needs what:

| Feature | Gateway SSH | Device SSH |
|---------|:-----------:|:----------:|
| Adaptive SQM | Required | - |
| WAN Speed Test (gateway-based) | Required | - |
| WAN Speed Test (server-based) | - | - |
| LAN Speed Test (gateway) | Required | - |
| LAN Speed Test (devices) | - | Required |
| Client Speed Test | - | - |
| Security Audit | - | - |
| Config Optimizer | - | - |
| Wi-Fi Optimizer | - | - |

### Enabling SSH in UniFi

**Important:** Both SSH settings must be configured via the UniFi Network web interface. These options are not available in the iOS or Android UniFi apps.

#### Gateway SSH (Console SSH)

Enables SSH access to Cloud Gateways (UCG, UDM, UDM Pro, etc.):

1. Open **UniFi Network**: `https://<gateway-ip>` or `https://unifi.ui.com`
2. Sign in to your Console
3. Click **Settings** on the bottom portion of the side menu
4. Navigate to **Control Plane** → **Console**
5. Enable **SSH** and set a secure password

Use `root` as the username and the password you set above.

**For UXG (non-Cloud Gateway):** Enable SSH using the Device SSH steps below, but enter those credentials in Network Optimizer's Gateway SSH settings.

#### Device SSH (UniFi Network 9.5+)

Enables SSH access to adopted devices (switches, access points, modems):

1. Open **UniFi Network**: `https://<gateway-ip>` or `https://unifi.ui.com`
2. Sign in to your Console
3. Click **UniFi Devices** on the side menu
4. In the left-hand filter menu, select **Device Updates and Settings** at the bottom
5. Expand **Device SSH Settings** at the bottom
6. Check **Device SSH Authentication**
7. Set a username and secure password (optionally add SSH public keys)
8. Save

**Note:** This is a separate credential from Gateway SSH.

### Configuring SSH in Network Optimizer

Once SSH is enabled in UniFi, enter the same credentials in Network Optimizer's **Settings** page.

#### Gateway SSH

1. Go to **Settings** → **Gateway SSH**
2. Enter your gateway's IP address, username (`root`), and the SSH password you set in UniFi
3. Click **Test SSH Connection** to verify connectivity
4. Click **Check iperf3 Status** to confirm iperf3 is available for speed tests

As an alternative to password authentication, you can provide a **Private Key Path** (e.g., `/app/ssh-keys/gateway_key`). Leave the password blank when using key-based authentication.

#### Device SSH

1. Go to **Settings** → **Device SSH**
2. Enter the username and password you configured in UniFi's Device SSH Settings
3. Click **Test SSH Connection** - it will automatically find a device on your network to test against

Private key authentication is also supported. Enter the key path (e.g., `/app/ssh-keys/id_rsa`) and leave the password blank.

### Per-Device SSH Overrides

In **LAN Speed Test**, when you add a custom speed test device and check **Start iperf3 server before test**, you can override the global Device SSH credentials for that specific device. Override fields include username, password, and private key path. Leave any field blank to fall back to the global Device SSH settings.

This is useful for non-UniFi equipment or devices with different credentials.

### Troubleshooting SSH Connections

If SSH connections are failing:

1. **Check credentials** - Use the **Test SSH Connection** button in Settings to verify your credentials are correct
2. **Check UniFi firewall rules** - Ensure SSH traffic is allowed between the Network Optimizer server and your gateway/devices
3. **Check CyberSecure IDS/IPS** - If your CyberSecure Detection Mode is set to **Notify and Block**, SSH connections may be blocked by the rule **"ET SCAN Potential SSH Scan OUTBOUND"**. You can fix this three ways:
   - **Recommended:** Look for blocked connections in **Insights → Flows**, then create a **Suppression** for this specific signature in the Logs section
   - **Alternative:** Add the Network Optimizer server's IP as a source in **Detection Exclusions**
   - **Alternative:** In CyberSecure settings, uncheck **Scanning Activity** under the Attacks and Reconnaissance category (this disables the entire category, so the suppression approach is preferred)

## Client Speed Testing (Optional)

Enable speed testing from any device on your LAN (phones, tablets, laptops, IoT devices) without requiring SSH access.

### Overview

Two methods are available:

| Method | Best For | Port |
|--------|----------|------|
| **OpenSpeedTest™** | Browser-based testing from any device | 3005 (configurable) |
| **iperf3 Server** | CLI testing with iperf3 clients | 5201 |

Results from both methods are stored in Network Optimizer and visible in the Client Speed Test page.

**Why separate containers?** OpenSpeedTest runs as its own container (not proxied through Network Optimizer) for performance reasons. Speed tests can push massive bandwidth (multi-gigabit to 100 Gbps on high-end networks), and routing that traffic through a reverse proxy or the .NET application would add overhead and reduce accuracy. The only data sent to Network Optimizer is the small JSON result payload after the test completes.

### OpenSpeedTest™ (Browser-Based)

Bundled as part of the Docker Compose stack. Access at `http://your-server:3005`.

**Configuration (in `.env`):**

```env
# Main app identity (feel free to omit N/A settings)
HOST_IP=192.168.1.100               # Optional - for path analysis if auto-detection fails
HOST_NAME=nas                       # Optional - friendly hostname (requires DNS)
REVERSE_PROXIED_HOST_NAME=...       # Optional - if main app is behind HTTPS proxy

# SpeedTest-specific (feel free to omit N/A settings)
OPENSPEEDTEST_PORT=3005             # Optional - change if port 3005 conflicts
OPENSPEEDTEST_HOST=speedtest.local  # Optional - if speedtest uses different hostname than main app
OPENSPEEDTEST_HTTPS=true            # Optional - if speedtest is behind TLS proxy (for geolocated speed test result map)
OPENSPEEDTEST_HTTPS_PORT=443        # Optional - HTTPS port if not 443
```

See `.env.example` for full documentation on each setting.

**Usage:**
1. Open `http://your-server:3005` from any device on your network
2. Run the speed test
3. Results automatically appear in Network Optimizer's Client Speed Test page

### HTTPS Configuration Requirements

When serving OpenSpeedTest over HTTPS (`OPENSPEEDTEST_HTTPS=true`), the main Network Optimizer app **must also be accessible via HTTPS**. This is a browser security requirement - HTTPS pages cannot make requests to HTTP endpoints (mixed active content).

**Valid Configurations:**

| Speedtest Protocol | Main App Protocol | Configuration Required |
|-------------------|-------------------|------------------------|
| HTTP | HTTP | `HOST_NAME` or `HOST_IP` |
| HTTP | HTTPS | `REVERSE_PROXIED_HOST_NAME` |
| HTTPS | HTTPS | `OPENSPEEDTEST_HTTPS=true` + `REVERSE_PROXIED_HOST_NAME` |
| HTTPS | HTTP | ❌ **Not supported** (browser blocks mixed content) |

**Example - Both behind HTTPS reverse proxy:**
```env
HOST_NAME=nas
REVERSE_PROXIED_HOST_NAME=optimizer.example.com
OPENSPEEDTEST_HOST=speedtest.example.com
OPENSPEEDTEST_HTTPS=true
```

**If you see this error in browser console:**
```
Blocked loading mixed active content "http://..."
```
It means your speedtest is HTTPS but trying to POST results to an HTTP endpoint. Set `REVERSE_PROXIED_HOST_NAME` to fix.

### iperf3 Server Mode

Run iperf3 as a server inside the Network Optimizer container for CLI-based testing.

**Enable in `.env`:**
```env
IPERF3_SERVER_ENABLED=true
```

**Usage from client devices:**
```bash
# Upload test (client to server, 4 streams)
iperf3 -c your-server -P 4

# Download test (server to client, 4 streams)
iperf3 -c your-server -P 4 -R

# Bidirectional test (runs both directions simultaneously)
iperf3 -c your-server -P 4 --bidir
```

Results are captured automatically and stored with client IP identification.

### Port Conflicts

**Before enabling these features, check for existing services using the same ports:**

```bash
# Check for iperf3 server already running
sudo netstat -tlnp | grep 5201
# or
sudo ss -tlnp | grep 5201

# Check for existing services on port 3005
sudo netstat -tlnp | grep 3005
docker ps | grep -E "3000|3005"
```

**Common conflicts:**

| Port | Service | Resolution |
|------|---------|------------|
| 5201 | Existing iperf3 server | Stop: `sudo systemctl stop iperf3` |
| 3005 | OpenSpeedTest port conflict | Set `OPENSPEEDTEST_PORT=3006` (or another free port) in `.env` |

**Container name conflicts:**

The bundled OpenSpeedTest uses container name `openspeedtest`. If you have an existing container with this name:

```bash
# Remove existing container
docker stop openspeedtest && docker rm openspeedtest

# Then start the Network Optimizer stack
docker compose up -d
```

### External WAN Speed Test Server (Optional)

Deploy an OpenSpeedTest instance to a remote server (VPS, cloud VM, etc.) to let clients test their **internet (WAN) speed** from any device on your network. Results are automatically posted back to your Network Optimizer instance.

**How it works:** The client's browser connects to the remote speed test server. Traffic flows: client → your WAN → internet → remote server → internet → your WAN → client. The result is posted back to Network Optimizer with a server identifier, and stored as a WAN speed test result.

**Requirements:**
- A remote server with Docker (any cloud VPS works)
- Port 3005 (or your chosen port) open on the remote server
- **HTTPS on the external server** (strongly recommended - see note below)

**Why HTTPS?** Chrome and Edge enforce [Private Network Access](https://developer.chrome.com/blog/private-network-access-update) rules. The speed test page is served from a public IP, and the browser posts results back to Network Optimizer on your LAN (a private IP). These browsers block this unless the page origin is HTTPS (a secure context). Firefox and Safari do not currently enforce this restriction, but HTTPS is still strongly recommended.

**Setup:**

1. In Network Optimizer, go to **Settings → External Speed Test Server**
2. Enter the server name, hostname/IP, port, and scheme (HTTPS)
3. Save - a **deploy command** will appear with everything pre-filled
4. SSH to your remote server and run the deploy command

The deploy command handles downloading files, building the container, and starting the server. The Server ID is automatically generated from the name you entered and links results back to this server.

**Interactive deploy** (if you haven't configured Settings yet, the script will walk you through it):
```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/deploy-external-speedtest.sh | bash
```

**Updating** an existing installation (re-downloads files and rebuilds the container):
```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/deploy-external-speedtest.sh | bash -s -- --update
```

**Setting up HTTPS:** If you use [NetworkOptimizer-Proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy) (Traefik), the WAN speed test route is already included in `config.example.yml` - just uncomment the `speedtest-wan` router and service, update the hostname and VPS address, and you're done. The config enforces HTTP/1.1 and strips compression headers automatically.

If you use a different reverse proxy, add a route for the external speed test hostname pointing to your remote server on port 3005. The reverse proxy must force HTTP/1.1 for accurate speed test results (HTTP/2 multiplexing interferes with throughput measurement).

Then update the external server settings in Network Optimizer to use `https` scheme and port `443`.

### Disabling Optional Services

To disable client speed testing components:

```env
# Disable iperf3 server (default)
IPERF3_SERVER_ENABLED=false

# To completely disable OpenSpeedTest, comment it out in docker-compose.yml
# or use a custom override file
```

## Next Steps

After deployment:
1. Access web UI and complete initial setup
2. Connect to UniFi Controller
3. Configure SSH access for gateway and devices (see above)
4. Run security audit
5. Configure SQM settings (if applicable)
6. Set up client speed testing (optional, see above)

See main documentation for feature guides.


================================================
FILE: docker/Dockerfile
================================================
# Multi-stage build for Network Optimizer
# Stage 1: Build stage with full .NET SDK
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

# Version passed from CI/CD (MinVer can't access .git in Docker context)
ARG VERSION=0.0.0-alpha.0

WORKDIR /src

# Copy solution file, build props, NuGet config, and all project files first for layer caching
COPY ["NetworkOptimizer.sln", "./"]
COPY ["Directory.Build.props", "./"]
COPY ["nuget.config", "./"]
COPY ["packages/", "packages/"]
COPY ["src/NetworkOptimizer.Core/NetworkOptimizer.Core.csproj", "src/NetworkOptimizer.Core/"]
COPY ["src/NetworkOptimizer.Web/NetworkOptimizer.Web.csproj", "src/NetworkOptimizer.Web/"]
COPY ["src/NetworkOptimizer.UniFi/NetworkOptimizer.UniFi.csproj", "src/NetworkOptimizer.UniFi/"]
COPY ["src/NetworkOptimizer.Audit/NetworkOptimizer.Audit.csproj", "src/NetworkOptimizer.Audit/"]
COPY ["src/NetworkOptimizer.Sqm/NetworkOptimizer.Sqm.csproj", "src/NetworkOptimizer.Sqm/"]
COPY ["src/NetworkOptimizer.Monitoring/NetworkOptimizer.Monitoring.csproj", "src/NetworkOptimizer.Monitoring/"]
COPY ["src/NetworkOptimizer.Storage/NetworkOptimizer.Storage.csproj", "src/NetworkOptimizer.Storage/"]
COPY ["src/NetworkOptimizer.Agents/NetworkOptimizer.Agents.csproj", "src/NetworkOptimizer.Agents/"]
COPY ["src/NetworkOptimizer.Reports/NetworkOptimizer.Reports.csproj", "src/NetworkOptimizer.Reports/"]
COPY ["src/NetworkOptimizer.Diagnostics/NetworkOptimizer.Diagnostics.csproj", "src/NetworkOptimizer.Diagnostics/"]
COPY ["src/NetworkOptimizer.WiFi/NetworkOptimizer.WiFi.csproj", "src/NetworkOptimizer.WiFi/"]
COPY ["src/NetworkOptimizer.Alerts/NetworkOptimizer.Alerts.csproj", "src/NetworkOptimizer.Alerts/"]
COPY ["src/NetworkOptimizer.Threats/NetworkOptimizer.Threats.csproj", "src/NetworkOptimizer.Threats/"]

# Restore dependencies
RUN dotnet restore "src/NetworkOptimizer.Web/NetworkOptimizer.Web.csproj"

# Copy all source files
COPY src/ src/

# Build and publish Web UI (includes all dependent projects)
# MinVerVersionOverride tells MinVer to use this version instead of reading git tags
RUN dotnet publish "src/NetworkOptimizer.Web/NetworkOptimizer.Web.csproj" \
    -c Release \
    -o /app/publish \
    --no-restore \
    -p:MinVerVersionOverride=${VERSION}

# Stage 2: Build uwnspeedtest Go binaries
# - Container's native arch: for server-side WAN speed tests (local execution)
# - linux/arm64: for gateway-direct WAN speed tests (deployed via SSH to UniFi gateways)
FROM golang:1.22-alpine AS uwnspeedtest-build
ARG VERSION=""
ARG TARGETARCH=amd64
WORKDIR /src
# Copy both modules (uwnspeedtest imports cfspeedtest/speedtest)
COPY src/cfspeedtest/ cfspeedtest/
COPY src/uwnspeedtest/ uwnspeedtest/
WORKDIR /src/uwnspeedtest
# Build for container's target architecture (local server-side tests)
# VERSION override only applies when explicitly passed (GHA releases); otherwise uses main.go static version
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -trimpath \
    -ldflags "-s -w ${VERSION:+-X main.version=${VERSION}}" \
    -o /uwnspeedtest-local .
# Build for gateway (always linux/arm64, deployed to UniFi gateways via SSH)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath \
    -ldflags "-s -w ${VERSION:+-X main.version=${VERSION}}" \
    -o /uwnspeedtest-gateway .

# Stage 2.5: Build wansteer Go binary (gateway-only, always linux/arm64)
# WAN Steering daemon - manages iptables rules to load-balance traffic across multiple WANs
FROM golang:1.22-alpine AS wansteer-build
ARG VERSION=""
WORKDIR /src
COPY src/wansteer/ wansteer/
WORKDIR /src/wansteer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath \
    -ldflags "-s -w ${VERSION:+-X main.version=${VERSION}}" \
    -o /wansteer-gateway .

# Stage 3: Build iperf3 from source for latest version
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS iperf-build

# iperf3 version - check https://github.com/esnet/iperf/releases for updates
ARG IPERF3_VERSION=3.20

RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Build iperf3 from source
WORKDIR /tmp
RUN curl -fLO --retry 3 --retry-delay 5 https://github.com/esnet/iperf/releases/download/${IPERF3_VERSION}/iperf-${IPERF3_VERSION}.tar.gz \
    && tar xzf iperf-${IPERF3_VERSION}.tar.gz \
    && cd iperf-${IPERF3_VERSION} \
    && ./configure --prefix=/usr/local \
    && make \
    && make install

# Stage 4: Runtime stage with ASP.NET runtime only
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
ARG TARGETARCH=amd64
WORKDIR /app

# Install necessary utilities (without iperf3 - we copy it from build)
# Note: openssh-client and sshpass removed - SSH.NET handles SSH natively
RUN apt-get update && apt-get install -y \
    curl \
    iputils-ping \
    libssl3 \
    gosu \
    sqlite3 \
    && rm -rf /var/lib/apt/lists/*

# Copy iperf3 from build stage
COPY --from=iperf-build /usr/local/bin/iperf3 /usr/local/bin/
COPY --from=iperf-build /usr/local/lib/libiperf* /usr/local/lib/
RUN ldconfig

# Copy published application
COPY --from=build /app/publish .

# Copy uwnspeedtest binaries:
# - Local binary for server-side WAN speed tests (matches container arch)
# - Gateway binary for deployment via SSH to UniFi gateways (always linux/arm64)
RUN mkdir -p /app/tools
COPY --from=uwnspeedtest-build /uwnspeedtest-local /app/tools/uwnspeedtest-linux-${TARGETARCH:-amd64}
COPY --from=uwnspeedtest-build /uwnspeedtest-gateway /app/tools/uwnspeedtest-linux-arm64

# Copy wansteer binary (gateway-only, deployed via SSH to UniFi gateways)
COPY --from=wansteer-build /wansteer-gateway /app/tools/wansteer-linux-arm64

# Create directories for volumes
RUN mkdir -p /app/data /app/ssh-keys /app/logs

# Set environment variables
ENV ASPNETCORE_ENVIRONMENT=Production \
    ASPNETCORE_HTTP_PORTS=8042 \
    DOTNET_RUNNING_IN_CONTAINER=true \
    DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

# Expose ports
EXPOSE 8042
EXPOSE 5201

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:8042/api/health || exit 1

# Set ownership for app directories
RUN chown -R app:app /app

# Copy entrypoint script
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Run as root initially; entrypoint will fix volume permissions then drop to app user
ENTRYPOINT ["/entrypoint.sh"]


================================================
FILE: docker/NATIVE-DEPLOYMENT.md
================================================
# Native Deployment Guide

Run Network Optimizer directly on the host without Docker for maximum network performance.

## When to Use Native Deployment

**Recommended for:**
- **macOS/Windows users** - Docker Desktop adds virtualization overhead that can limit network throughput
- **Speed test accuracy** - Native deployment provides accurate multi-gigabit measurements
- **Low-overhead systems** - Minimal resource usage without container overhead
- **Dedicated appliances** - Purpose-built network monitoring devices

**Use Docker instead if:**
- You prefer containerized deployments
- You need easy updates via image pulls
- Your network speeds are under 2 Gbps (except macOS - see below)

**macOS note:** Docker Desktop limits network throughput for speed testing. For accurate multi-gigabit measurements on macOS, use native deployment. The native install script includes OpenSpeedTest setup, so you get both maximum performance and browser-based speed testing.

## Platform-Specific Instructions

- [macOS Deployment](#macos-deployment)
- [Linux Deployment](#linux-deployment)
- [Windows Deployment](#windows-deployment) - Use the Windows Installer instead

---

## macOS Deployment

For the quickest macOS installation, see [macOS Installation Guide](../docs/MACOS-INSTALLATION.md).

For manual installation or customization, continue with the steps below.

---

### Manual Installation

### Prerequisites

**System Requirements:**
- macOS 11 (Big Sur) or later
- Intel or Apple Silicon (M1/M2/M3)
- 2GB RAM minimum
- 1GB disk space

**Required Software:**
```bash
# Install Homebrew if not present
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install required tools
brew install sshpass iperf3
```

### Build from Source

```bash
# Install .NET SDK (if not present)
brew install dotnet

# Clone repository
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
# or via SSH: git clone git@github.com:Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer

# Build for your architecture
# Apple Silicon (M1/M2/M3):
dotnet publish src/NetworkOptimizer.Web -c Release -r osx-arm64 --self-contained -o ~/network-optimizer

# Intel Macs:
# dotnet publish src/NetworkOptimizer.Web -c Release -r osx-x64 --self-contained -o ~/network-optimizer

cd ~/network-optimizer
```

### Code Signing

macOS requires binaries to be signed. Sign with an ad-hoc signature:

```bash
cd ~/network-optimizer

# Sign all dynamic libraries
find . -name '*.dylib' -exec codesign --force --sign - {} \;

# Sign main executable
codesign --force --sign - NetworkOptimizer.Web

# Verify signature
codesign -v NetworkOptimizer.Web
```

### Create Startup Script

```bash
cat > ~/network-optimizer/start.sh << 'EOF'
#!/bin/bash
cd "$(dirname "$0")"

# Add Homebrew to PATH
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"

# Environment configuration
export TZ="America/Chicago"  # Change to your timezone
export ASPNETCORE_URLS="http://0.0.0.0:8042"

# Host IP - required for iperf3 client result tracking
export HOST_IP="192.168.1.100"  # Change to this Mac's IP address

# Enable iperf3 server for client speed testing (port 5201)
export Iperf3Server__Enabled=true

# Optional: Set admin password (otherwise auto-generated on first run)
# export APP_PASSWORD="your-secure-password"

# Start the application
./NetworkOptimizer.Web
EOF

chmod +x ~/network-optimizer/start.sh
```

### Create Log Directory

```bash
mkdir -p ~/network-optimizer/logs
```

### Install as System Service (launchd)

Create the service definition:

```bash
cat > ~/Library/LaunchAgents/com.networkoptimizer.app.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.networkoptimizer.app</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/YOUR_USERNAME/network-optimizer/start.sh</string>
    </array>
    <key>WorkingDirectory</key>
    <string>/Users/YOUR_USERNAME/network-optimizer</string>
    <key>KeepAlive</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/YOUR_USERNAME/network-optimizer/logs/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/YOUR_USERNAME/network-optimizer/logs/stderr.log</string>
</dict>
</plist>
EOF
```

**Important:** Replace `YOUR_USERNAME` with your actual username:

```bash
sed -i '' "s/YOUR_USERNAME/$(whoami)/g" ~/Library/LaunchAgents/com.networkoptimizer.app.plist
```

### Start the Service

```bash
# Load and start the service
launchctl load ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# Verify it's running
launchctl list | grep networkoptimizer

# Check health
curl -s http://localhost:8042/api/health
```

### Access the Application

Open your browser to: **http://localhost:8042**

On first run, check the logs for the auto-generated admin password:
```bash
grep -A5 "AUTO-GENERATED" ~/network-optimizer/logs/stdout.log
```

### Service Management

```bash
# Stop service
launchctl unload ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# Start service
launchctl load ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# Restart service
launchctl unload ~/Library/LaunchAgents/com.networkoptimizer.app.plist && \
launchctl load ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# View logs
tail -f ~/network-optimizer/logs/stdout.log

# Check status
launchctl list | grep networkoptimizer && curl -s http://localhost:8042/api/health
```

### Data Location

Network Optimizer stores data in:
- **Database:** `~/Library/Application Support/NetworkOptimizer/network_optimizer.db`
- **Credentials:** `~/Library/Application Support/NetworkOptimizer/.credential_key`
- **Logs:** `~/network-optimizer/logs/`

### Updating

```bash
# Stop service
launchctl unload ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# Backup database (optional)
cp ~/Library/Application\ Support/NetworkOptimizer/network_optimizer.db ~/network_optimizer.db.backup

# Pull latest from main and rebuild
cd ~/NetworkOptimizer
git fetch origin && git checkout main && git pull
dotnet publish src/NetworkOptimizer.Web -c Release -r osx-arm64 --self-contained -o ~/network-optimizer

# Re-sign binaries
cd ~/network-optimizer
find . -name '*.dylib' -exec codesign --force --sign - {} \;
codesign --force --sign - NetworkOptimizer.Web

# Start service
launchctl load ~/Library/LaunchAgents/com.networkoptimizer.app.plist
```

### Uninstall

```bash
# Stop and remove service
launchctl unload ~/Library/LaunchAgents/com.networkoptimizer.app.plist
rm ~/Library/LaunchAgents/com.networkoptimizer.app.plist

# Remove application
rm -rf ~/network-optimizer

# Remove data (optional - keeps your settings if you reinstall)
rm -rf ~/Library/Application\ Support/NetworkOptimizer
```

---

## Linux Deployment

### Prerequisites

**System Requirements:**
- Ubuntu 20.04+, Debian 11+, RHEL 8+, or compatible
- x64 or ARM64 architecture
- 2GB RAM minimum
- 1GB disk space

**Required Software:**
```bash
# Debian/Ubuntu
sudo apt update
sudo apt install -y sshpass iperf3

# RHEL/CentOS/Fedora
sudo dnf install -y epel-release
sudo dnf install -y sshpass iperf3
```

### Build from Source

```bash
# Install .NET SDK
# Debian/Ubuntu:
wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 10.0
export PATH="$HOME/.dotnet:$PATH"

# Clone and build
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
# or via SSH: git clone git@github.com:Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer

# Create installation directory
sudo mkdir -p /opt/network-optimizer
sudo chown $USER:$USER /opt/network-optimizer

# Build for your architecture (x64)
dotnet publish src/NetworkOptimizer.Web -c Release -r linux-x64 --self-contained -o /opt/network-optimizer

# For ARM64, use:
# dotnet publish src/NetworkOptimizer.Web -c Release -r linux-arm64 --self-contained -o /opt/network-optimizer

# Make executable
chmod +x /opt/network-optimizer/NetworkOptimizer.Web
```

### Create Startup Script

```bash
cat > /opt/network-optimizer/start.sh << 'EOF'
#!/bin/bash
cd "$(dirname "$0")"

# Environment configuration
export TZ="America/Chicago"  # Change to your timezone
export ASPNETCORE_URLS="http://0.0.0.0:8042"

# Host IP - required for iperf3 client result tracking
export HOST_IP="192.168.1.100"  # Change to this server's IP address

# Enable iperf3 server for client speed testing (port 5201)
export Iperf3Server__Enabled=true

# Optional: Set admin password
# export APP_PASSWORD="your-secure-password"

# Start the application
./NetworkOptimizer.Web
EOF

chmod +x /opt/network-optimizer/start.sh
```

### Install as System Service (systemd)

```bash
sudo cat > /etc/systemd/system/network-optimizer.service << 'EOF'
[Unit]
Description=Network Optimizer
After=network.target

[Service]
Type=simple
User=YOUR_USERNAME
WorkingDirectory=/opt/network-optimizer
ExecStart=/opt/network-optimizer/start.sh
Restart=always
RestartSec=10
StandardOutput=append:/opt/network-optimizer/logs/stdout.log
StandardError=append:/opt/network-optimizer/logs/stderr.log

[Install]
WantedBy=multi-user.target
EOF

# Replace YOUR_USERNAME
sudo sed -i "s/YOUR_USERNAME/$USER/g" /etc/systemd/system/network-optimizer.service

# Create log directory
mkdir -p /opt/network-optimizer/logs

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable network-optimizer
sudo systemctl start network-optimizer
```

### Service Management

```bash
# Check status
sudo systemctl status network-optimizer

# Stop
sudo systemctl stop network-optimizer

# Start
sudo systemctl start network-optimizer

# Restart
sudo systemctl restart network-optimizer

# View logs
tail -f /opt/network-optimizer/logs/stdout.log
journalctl -u network-optimizer -f
```

### Data Location

- **Database:** `~/.local/share/NetworkOptimizer/network_optimizer.db`
- **Credentials:** `~/.local/share/NetworkOptimizer/.credential_key`
- **Logs:** `/opt/network-optimizer/logs/`

---

## Windows Deployment

**Use the Windows Installer instead of manual deployment.**

Download the MSI installer from [GitHub Releases](https://github.com/Ozark-Connect/NetworkOptimizer/releases). The installer provides:

- One-click installation
- Automatic Windows Service setup (starts at boot)
- Bundled iperf3 for speed testing
- Proper uninstall via Windows Settings

After installation, access the web UI at **http://localhost:8042** (or use the machine's IP/hostname from other devices).

---

## Client Speed Testing

Native deployments support both browser-based and CLI-based client speed testing.

### OpenSpeedTest™ (Browser-Based)

The macOS install script (`scripts/install-macos-native.sh`) automatically sets up OpenSpeedTest with nginx, providing browser-based speed testing from any device - no client software required.

After installation, access SpeedTest at: **http://your-mac-ip:3005**

For manual setup or Linux, see [Manual OpenSpeedTest Setup](#manual-openspeedtest-setup) below.

### iperf3 Server Mode

For CLI-based testing with iperf3 clients.

### Enable iperf3 Server Mode

Add to your startup script:
```bash
export Iperf3Server__Enabled=true
```

### Port Conflicts

If you already have an iperf3 server running:
```bash
# Linux - stop existing service
sudo systemctl stop iperf3

# Check if port 5201 is in use
sudo ss -tlnp | grep 5201
```

### Testing from Clients

From any device with iperf3 installed:
```bash
# Download test
iperf3 -c your-server-ip

# Upload test
iperf3 -c your-server-ip -R
```

Results appear in Network Optimizer's Client Speed Test page.

### Manual OpenSpeedTest Setup

If you installed manually (without the install script), you can set up OpenSpeedTest:

**macOS:**
```bash
# Install nginx
brew install nginx

# Create SpeedTest directory
mkdir -p ~/network-optimizer/SpeedTest/{conf,logs,temp,html/assets/{css,js,fonts,images/icons}}
cd ~/network-optimizer/SpeedTest

# Copy files from repo (adjust path as needed)
REPO=~/NetworkOptimizer
cp $REPO/src/NetworkOptimizer.Installer/SpeedTest/nginx.conf conf/
cp $REPO/src/NetworkOptimizer.Installer/SpeedTest/nginx/conf/mime.types conf/
cp $REPO/src/OpenSpeedTest/{index.html,hosted.html,downloading,upload} html/
cp -r $REPO/src/OpenSpeedTest/assets/* html/assets/

# Create config.js with your server's IP
cat > html/assets/js/config.js << 'EOF'
window.NETWORK_OPTIMIZER_CONFIG = {
    resultsApiUrl: "http://YOUR_IP:8042/api/public/speedtest/results"
};
EOF

# Start nginx
nginx -c ~/network-optimizer/SpeedTest/conf/nginx.conf -p ~/network-optimizer/SpeedTest
```

**Linux:**
```bash
# Install nginx
sudo apt install nginx  # Debian/Ubuntu
# or
sudo dnf install nginx  # RHEL/Fedora

# Create SpeedTest directory
sudo mkdir -p /opt/network-optimizer/SpeedTest/{conf,logs,temp,html/assets/{css,js,fonts,images/icons}}
sudo chown -R $USER: /opt/network-optimizer/SpeedTest

# Copy files from repo and create config.js (same as macOS, adjust paths)
# Start nginx with the SpeedTest config
sudo nginx -c /opt/network-optimizer/SpeedTest/conf/nginx.conf -p /opt/network-optimizer/SpeedTest
```

Access SpeedTest at `http://your-server:3005`. Results automatically appear in Network Optimizer.

## Firewall Configuration

Ensure port 8042 (or your configured port) is accessible:

**macOS:**
```bash
# Usually not needed for local access
# For remote access, allow in System Preferences > Security & Privacy > Firewall
```

**Linux (UFW):**
```bash
sudo ufw allow 8042/tcp
```

**Linux (firewalld):**
```bash
sudo firewall-cmd --permanent --add-port=8042/tcp
sudo firewall-cmd --reload
```

**Windows:**
```powershell
netsh advfirewall firewall add rule name="Network Optimizer" dir=in action=allow protocol=tcp localport=8042
```

---

## Reverse Proxy (Optional)

For HTTPS access, place behind a reverse proxy like Caddy, nginx, or Traefik.

### Caddy Example

```caddy
network-optimizer.example.com {
    reverse_proxy localhost:8042
}
```

### nginx Example

```nginx
server {
    listen 443 ssl http2;
    server_name network-optimizer.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://localhost:8042;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

---

## Troubleshooting

### macOS: "Killed: 9" Error

The binary needs code signing:
```bash
find ~/network-optimizer -name '*.dylib' -exec codesign --force --sign - {} \;
codesign --force --sign - ~/network-optimizer/NetworkOptimizer.Web
```

### macOS: sshpass/iperf3 Not Found

Add Homebrew to PATH in `start.sh`:
```bash
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
```

### Linux: Permission Denied

```bash
chmod +x /opt/network-optimizer/NetworkOptimizer.Web
chmod +x /opt/network-optimizer/start.sh
```

### All Platforms: Port Already in Use

Change the port in your startup script:
```bash
export ASPNETCORE_URLS="http://0.0.0.0:8080"  # Use different port
```

### Check Application Logs

```bash
# macOS
tail -f ~/network-optimizer/logs/stdout.log

# Linux
tail -f /opt/network-optimizer/logs/stdout.log
journalctl -u network-optimizer -f

# Windows
type C:\NetworkOptimizer\logs\stdout.log
```

### Reset Admin Password

If you forget the admin password, use the reset script:

```bash
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash
```

The script auto-detects macOS or Linux native installations, clears the password, restarts the service, and displays the new temporary password. Use `--macos` or `--linux` to force a specific mode.

**Manual fallback:**

```bash
# macOS
launchctl unload ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist
sqlite3 ~/Library/Application\ Support/NetworkOptimizer/network_optimizer.db \
    "UPDATE AdminSettings SET Password = NULL, Enabled = 0;"
launchctl load ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist
grep "Password:" ~/network-optimizer/logs/stdout.log | tail -1

# Linux
sudo systemctl stop network-optimizer
sqlite3 /opt/network-optimizer/data/network_optimizer.db \
    "UPDATE AdminSettings SET Password = NULL, Enabled = 0;"
sudo systemctl start network-optimizer
journalctl -u network-optimizer --since "2 minutes ago" | grep "Password:"
```

---

## Support

- Documentation: See `docs/` folder in repository
- GitHub Issues: https://github.com/Ozark-Connect/NetworkOptimizer/issues
- Email: tj@ozarkconnect.net


================================================
FILE: docker/QUICK-REFERENCE.md
================================================
# Network Optimizer - Quick Reference Card

## Quick Start

### Option A: Pull Docker Image (Recommended)

**Linux / Windows:**
```bash
mkdir network-optimizer && cd network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
docker compose up -d
```

**macOS:**
```bash
mkdir network-optimizer && cd network-optimizer
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.macos.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
docker compose -f docker-compose.macos.yml up -d
```

### Option B: Build from Source

**Linux / Windows:**
```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
docker compose build && docker compose up -d
```

**macOS:**
```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
docker compose -f docker-compose.macos.yml build
docker compose -f docker-compose.macos.yml up -d
```

### First Run - Get Admin Password
```bash
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

Access at: **http://localhost:8042**

## Common Commands

### Service Management
```bash
docker-compose up -d        # Start
docker-compose down         # Stop
docker-compose restart      # Restart
docker-compose ps           # Check status
```

### Logs
```bash
docker-compose logs -f network-optimizer
```

### Updates

**Docker Image:**
```bash
docker compose pull && docker compose up -d
```

**From Source:**
```bash
git pull && docker compose build && docker compose up -d
```

## Configuration

**Environment File:** `.env` (optional)

```bash
cp .env.example .env
nano .env
docker-compose up -d
```

**Key Settings:**
```env
WEB_PORT=8042              # Web UI port
TZ=America/Chicago         # Timezone
APP_PASSWORD=              # Optional preset password
HOST_IP=                   # Required for bridge networking
```

## Admin Password

**Priority order:**
1. Database password (Settings → Admin Password) - recommended
2. `APP_PASSWORD` environment variable
3. Auto-generated on first run (check logs)

**Set permanent password:**
1. Log in with auto-generated password from logs
2. Go to Settings → Admin Password
3. Enter and save new password

## Troubleshooting

### Service Won't Start
```bash
docker-compose down
docker-compose up -d
docker-compose logs -f network-optimizer
```

### Port Already in Use
```bash
# Edit .env
WEB_PORT=8090

docker-compose up -d
```

### Reset Everything
```bash
docker-compose down -v
rm -rf data/
docker-compose up -d
```

## Client Speed Testing

### Browser-Based (OpenSpeedTest™)
Access at: **http://localhost:3005** (port configurable via `OPENSPEEDTEST_PORT`)

Configure in `.env` (also enforces canonical URL via 302 redirect):
```env
HOST_IP=192.168.1.100       # For path analysis (if auto-detect fails)
HOST_NAME=nas               # Canonical URL + friendlier URLs (needs DNS)
REVERSE_PROXIED_HOST_NAME=optimizer.example.com  # If behind proxy (https)
```

To disable: comment out `openspeedtest` service in `docker-compose.yml`

### CLI-Based (iperf3)
Enable in `.env`:
```env
IPERF3_SERVER_ENABLED=true
```

Test from clients:
```bash
iperf3 -c your-server      # Download
iperf3 -c your-server -R   # Upload
```

## Important Files

| File | Purpose |
|------|---------|
| `.env` | Configuration (optional) |
| `data/` | SQLite database, credentials |
| `logs/` | Application logs |
| `ssh-keys/` | SSH keys for device access |

## Health Check

```bash
docker-compose ps
curl http://localhost:8042/api/health
```

## Backup & Restore

### Backup
```bash
tar czf backup-$(date +%Y%m%d).tar.gz data/
```

### Restore
```bash
docker-compose down
tar xzf backup-YYYYMMDD.tar.gz
docker-compose up -d
```

## Security Checklist

- [ ] Set permanent password in Settings
- [ ] Firewall configured (allow 8042/tcp)
- [ ] HTTPS via reverse proxy (production)
- [ ] Regular backups of `data/` directory

## Docker Commands

```bash
docker-compose up -d           # Start in background
docker-compose down            # Stop and remove
docker-compose restart         # Restart
docker-compose exec network-optimizer bash  # Shell into container
docker stats                   # Resource usage
docker system prune            # Clean up unused objects
```

## Getting Help

- **Logs**: `docker-compose logs -f network-optimizer`
- **Health**: `curl http://localhost:8042/api/health`
- **GitHub**: https://github.com/Ozark-Connect/NetworkOptimizer

## System Requirements

- Docker 20.10+
- Docker Compose 2.0+
- 1GB RAM minimum
- 500MB disk minimum


================================================
FILE: docker/README.md
================================================
# Network Optimizer Docker Deployment

Complete Docker infrastructure for the Ozark Connect Network Optimizer for UniFi.

## Quick Start

### Option A: Pull Docker Image (Recommended)

The fastest way to get started. No build required.

**Linux / Windows:**
```bash
mkdir network-optimizer && cd network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
docker compose up -d
```

**macOS:**
```bash
mkdir network-optimizer && cd network-optimizer
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.macos.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
docker compose -f docker-compose.macos.yml up -d
```

### Option B: Build from Source

Clone the repository and build locally.

**Linux / Windows:**
```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
cp .env.example .env
docker compose build
docker compose up -d
```

**macOS:**

macOS doesn't support `network_mode: host`, so use the macOS-specific compose file:

```bash
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker
cp .env.example .env
docker compose -f docker-compose.macos.yml build
docker compose -f docker-compose.macos.yml up -d
```

### First Run

1. **Get the auto-generated admin password:**
   ```bash
   docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
   ```
   On first run, a secure password is generated and displayed in the logs.

2. **Access the Web UI:**
   - Network Optimizer: http://localhost:8042 (use password from logs)
   - Wait ~60 seconds on first startup

3. **Set a permanent password:**
   After logging in, go to Settings → Admin Password to set your own password (recommended).

**No `.env` file required** - defaults work out of the box. Optionally edit `.env` to set `APP_PASSWORD` or timezone.

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                    Docker Compose Stack                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   Network Optimizer                       │  │
│  │  - Blazor Web UI :8042                                    │  │
│  │  - iperf3 Server :5201 (optional)                         │  │
│  │  - SQLite Database (persistent in ./data)                 │  │
│  │  - Security Auditing, SQM, Speed Tests                    │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   OpenSpeedTest                           │  │
│  │  - Browser-based speed test :3005 (configurable)          │  │
│  │  - Results sent to Network Optimizer API                  │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

## Services

### Network Optimizer (Port 8042)

The main application providing:
- **Web UI**: Blazor Server web interface
  - Dashboard and monitoring
  - SQM configuration and management
  - Security audit results
  - Speed testing with path analysis
  - Report generation

**Volumes:**
- `./data` → `/app/data` - SQLite database, configurations
- `./ssh-keys` → `/app/ssh-keys` - SSH keys for agent deployment (optional)
- `./logs` → `/app/logs` - Application logs

### OpenSpeedTest (Port 3005, configurable)

Browser-based speed testing from any device. Results are automatically sent to Network Optimizer.

**Configuration:** Set `HOST_NAME` in `.env` for canonical URL enforcement and friendlier URLs. Set `HOST_IP` if path analysis can't auto-detect the server IP (bridge networking). See [Client Speed Testing](DEPLOYMENT.md#client-speed-testing-optional) for details.

**HTTPS Note:** If serving OpenSpeedTest over HTTPS (`OPENSPEEDTEST_HTTPS=true`), you must also set `REVERSE_PROXIED_HOST_NAME` so the main app is accessible via HTTPS. Browsers block mixed content (HTTPS pages cannot POST to HTTP endpoints). See [HTTPS Configuration Requirements](DEPLOYMENT.md#https-configuration-requirements).

**To disable:** Comment out the `openspeedtest` service in `docker-compose.yml`.

See [Client Speed Testing](DEPLOYMENT.md#client-speed-testing-optional) for full setup details.

## Admin Authentication

The web UI requires authentication. Password sources (in priority order):

1. **Database password** - Set via Settings → Admin Password (recommended)
2. **Environment variable** - Set `APP_PASSWORD` in `.env`
3. **Auto-generated** - On first run, a secure password is generated and shown in logs

### First Run
```bash
# View the auto-generated password (shown only once on first startup)
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

### Setting a Permanent Password
1. Log in with the auto-generated password
2. Go to Settings → Admin Password
3. Enter and confirm your new password
4. Click Save

### Using Environment Variable
Alternatively, set `APP_PASSWORD` in `.env`:
```env
APP_PASSWORD=your_secure_password
```

**Note:** Database passwords override the environment variable. Clear the database password in Settings to use `APP_PASSWORD`.

### Reset Admin Password

If you've forgotten your password or need to reset it:

```bash
# Clear the password from the database
docker exec network-optimizer sqlite3 /app/data/network_optimizer.db "UPDATE AdminSettings SET Password = NULL;"

# Restart to trigger auto-generated password
cd /path/to/network-optimizer/docker
docker compose up -d

# View the new auto-generated password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
```

## Configuration

### Environment Variables

See `.env.example` for all available options. Key variables:

```env
WEB_PORT=8042           # Blazor web UI (default)
TZ=America/Chicago      # Your timezone
APP_PASSWORD=           # Optional: preset admin password (otherwise auto-generated)
HOST_IP=                # Required for bridge networking (path analysis)
```

### Volume Mounts

#### Persistent Data
The `./data` directory contains:
- SQLite database (configs, audit results)
- Encrypted credentials
- Application state

**Backup:** Regular backups of `./data` directory recommended.

#### SSH Keys (Optional)
Place SSH keys in `./ssh-keys/` for automated agent deployment:
```bash
./ssh-keys/
├── id_rsa          # Private key
└── id_rsa.pub      # Public key
```

Set permissions:
```bash
chmod 600 ./ssh-keys/id_rsa
chmod 644 ./ssh-keys/id_rsa.pub
```

## Management

### Starting the Stack
```bash
docker-compose up -d
```

### Stopping the Stack
```bash
docker-compose down
```

### View Logs
```bash
docker-compose logs -f network-optimizer
```

### Restart a Service
```bash
docker-compose restart network-optimizer
```

### Update Images
```bash
docker-compose pull
docker-compose up -d
```

### Health Checks
```bash
docker-compose ps
curl http://localhost:8042/api/health
```

Healthy output:
```
NAME                          STATUS
network-optimizer             Up (healthy)
```

## Troubleshooting

### Service Won't Start

**Check logs:**
```bash
docker-compose logs <service-name>
```

**Common issues:**
1. **Port conflicts:** Another service using 8042
   - Solution: Change `WEB_PORT` in `.env`
2. **Permission errors:** Cannot write to volumes
   - Solution: `chmod` the directories or check Docker volume permissions

### Reset Everything

**Complete reset (deletes all data):**
```bash
docker-compose down -v
rm -rf data/
docker-compose up -d
```

## Security Considerations

### Production Deployment

1. **Set a strong admin password** via Settings → Admin Password after first login
2. **Restrict network access:**
   - Use firewall rules to limit who can access port 8042
   - Consider reverse proxy with SSL (nginx, Caddy, Traefik)
3. **Enable HTTPS** with reverse proxy:
   ```nginx
   server {
       listen 443 ssl;
       server_name network-optimizer.example.com;

       ssl_certificate /path/to/cert.pem;
       ssl_certificate_key /path/to/key.pem;

       location / {
           proxy_pass http://localhost:8042;
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "upgrade";
       }
   }
   ```
4. **Backup regularly:** Back up the `./data` directory which contains the SQLite database and credentials.

## Upgrading

### Standard Updates

```bash
docker compose down
docker compose pull
docker compose up -d
```

Data persists in the `./data` volume.

### Migrating from Build-from-Source

If you've been building locally with `docker compose build` and want to switch to pre-built images, see the [Migration Guide](DEPLOYMENT.md#migrating-from-build-from-source-to-pre-built-images). A simple `docker compose pull` won't work because your locally-built image already has the registry tag.

### Before Major Updates

```bash
# Backup data first
tar czf backup-$(date +%Y%m%d).tar.gz data/
```

## Support

For issues, feature requests, or questions:
- GitHub: https://github.com/Ozark-Connect/NetworkOptimizer
- Documentation: See `docs/` folder in repository

## License

Business Source License 1.1. See [LICENSE](../LICENSE) in the repository root.

© 2026 Ozark Connect


================================================
FILE: docker/docker-compose.local.yml
================================================
# Local Development Configuration
#
# Usage:
#   cd docker
#   docker compose -f docker-compose.local.yml build
#   docker compose -f docker-compose.local.yml up -d
#
# Access at: http://localhost:8042
#
# Note: Uses bridge networking with port mapping.
# Set HOST_IP in .env to your machine's IP for accurate path analysis.

services:
  network-optimizer:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    image: ozark-connect/network-optimizer:latest
    container_name: network-optimizer
    restart: unless-stopped
    ports:
      - "${WEB_PORT:-8042}:8042"      # Blazor web UI
    volumes:
      - ./data:/app/data              # SQLite, configs, license
      - ./ssh-keys:/app/ssh-keys      # Optional: SSH keys for agent deployment
      - ./logs:/app/logs              # Application logs
    environment:
      - TZ=${TZ:-UTC}
      - APP_PASSWORD=${APP_PASSWORD:-}
      - DEMO_MODE_MAPPINGS=${DEMO_MODE_MAPPINGS:-}
      # Host IP/name for path analysis and CORS (required for client speed tests)
      - HOST_IP=${HOST_IP:-}
      - HOST_NAME=${HOST_NAME:-}
      # Reverse proxy hostname (e.g., optimizer.example.com) - overrides HOST_NAME for API URLs
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
      # OpenSpeedTest configuration (for UI display and CORS)
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # iperf3 server mode - enable to accept client-initiated speed tests on port 5201
      - Iperf3Server__Enabled=${IPERF3_SERVER_ENABLED:-false}
      # Logging (Trace, Debug, Information, Warning, Error, Critical)
      - Logging__LogLevel__Default=${LOG_LEVEL:-Information}
      - Logging__LogLevel__NetworkOptimizer=${APP_LOG_LEVEL:-Information}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8042/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Network Optimizer Speed Test - customized OpenSpeedTest that sends results to Network Optimizer
  # Requires HOST_IP or HOST_NAME to be set in .env for result reporting
  network-optimizer-speedtest:
    build:
      context: ..
      dockerfile: docker/openspeedtest/Dockerfile
    image: ozark-connect/speedtest:latest
    container_name: network-optimizer-speedtest
    restart: unless-stopped
    ports:
      - "${OPENSPEEDTEST_PORT:-3005}:3000"
    environment:
      - TZ=${TZ:-UTC}
      # For URL construction and host enforcement redirect
      - HOST_NAME=${HOST_NAME:-}
      - HOST_IP=${HOST_IP:-}
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # For result reporting URL
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # InfluxDB and Grafana available if needed for future monitoring features
  # Uncomment below when time-series metrics are implemented

  # influxdb:
  #   image: influxdb:2.8
  #   container_name: network-optimizer-influxdb
  #   restart: unless-stopped
  #   ports:
  #     - "${INFLUXDB_PORT:-8086}:8086"
  #   volumes:
  #     - influxdb-data:/var/lib/influxdb2
  #     - influxdb-config:/etc/influxdb2
  #   environment:
  #     - DOCKER_INFLUXDB_INIT_MODE=setup
  #     - DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_USERNAME:-admin}
  #     - DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_PASSWORD}
  #     - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN}
  #     - DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG:-network-optimizer}
  #     - DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET:-network_optimizer}
  #     - DOCKER_INFLUXDB_INIT_RETENTION=${INFLUXDB_RETENTION:-30d}
  #     - TZ=${TZ:-UTC}
  #   networks:
  #     - network-optimizer
  #   healthcheck:
  #     test: ["CMD", "influx", "ping"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 60s

  # grafana:
  #   image: grafana/grafana:latest
  #   container_name: network-optimizer-grafana
  #   restart: unless-stopped
  #   ports:
  #     - "${GRAFANA_PORT:-3000}:3000"
  #   volumes:
  #     - grafana-data:/var/lib/grafana
  #     - ./grafana/provisioning:/etc/grafana/provisioning:ro
  #     - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
  #   environment:
  #     - GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME:-admin}
  #     - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
  #     - GF_USERS_ALLOW_SIGN_UP=false
  #     - GF_AUTH_ANONYMOUS_ENABLED=true
  #     - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
  #     - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/var/lib/grafana/dashboards/network-overview.json
  #     - TZ=${TZ:-UTC}
  #   networks:
  #     - network-optimizer
  #   depends_on:
  #     influxdb:
  #       condition: service_healthy
  #   healthcheck:
  #     test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 3
  #     start_period: 60s

# networks:
#   network-optimizer:
#     driver: bridge

# volumes:
#   influxdb-data:
#     driver: local
#   influxdb-config:
#     driver: local
#   grafana-data:
#     driver: local


================================================
FILE: docker/docker-compose.macos.yml
================================================
# macOS Development/Testing Configuration
#
# Usage:
#   cd docker
#   docker compose -f docker-compose.macos.yml build
#   docker compose -f docker-compose.macos.yml up -d
#
# Access at: http://localhost:8042
#
# Note: macOS doesn't support network_mode: host, so we use port mapping instead.

services:
  network-optimizer:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    image: ghcr.io/ozark-connect/network-optimizer:latest
    container_name: network-optimizer
    restart: unless-stopped
    ports:
      - "8042:8042"                       # Blazor web UI
    volumes:
      - ./data:/app/data                  # SQLite, configs, license
      - ./ssh-keys:/app/ssh-keys            # Optional: SSH keys for agent deployment
      - ./logs:/app/logs                  # Application logs
    environment:
      - TZ=${TZ:-America/Chicago}
      - APP_PASSWORD=${APP_PASSWORD:-}
      - DEMO_MODE_MAPPINGS=${DEMO_MODE_MAPPINGS:-}
      # Host IP/name for path analysis and CORS (required for client speed tests)
      - HOST_IP=${HOST_IP:-}
      - HOST_NAME=${HOST_NAME:-}
      # Reverse proxy hostname (e.g., optimizer.example.com) - overrides HOST_NAME for API URLs
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
      # OpenSpeedTest configuration (for UI display and CORS)
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # iperf3 server mode - enable to accept client-initiated speed tests on port 5201
      - Iperf3Server__Enabled=${IPERF3_SERVER_ENABLED:-false}
      # Logging (Trace, Debug, Information, Warning, Error, Critical)
      - Logging__LogLevel__Default=${LOG_LEVEL:-Information}
      - Logging__LogLevel__NetworkOptimizer=${APP_LOG_LEVEL:-Information}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8042/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Network Optimizer Speed Test - customized OpenSpeedTest that sends results to Network Optimizer
  # Requires HOST_IP or HOST_NAME to be set in .env for result reporting
  network-optimizer-speedtest:
    build:
      context: ..
      dockerfile: docker/openspeedtest/Dockerfile
    image: ghcr.io/ozark-connect/speedtest:latest
    container_name: network-optimizer-speedtest
    restart: unless-stopped
    ports:
      - "${OPENSPEEDTEST_PORT:-3005}:3000"
    environment:
      - TZ=${TZ:-America/Chicago}
      # For URL construction and host enforcement redirect
      - HOST_NAME=${HOST_NAME:-}
      - HOST_IP=${HOST_IP:-}
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # For result reporting URL
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # InfluxDB and Grafana available if needed for monitoring
  # Uncomment below and run: docker compose -f docker-compose.macos.yml up -d

  # influxdb:
  #   image: influxdb:2.7
  #   container_name: network-optimizer-influxdb
  #   restart: unless-stopped
  #   ports:
  #     - "8086:8086"
  #   volumes:
  #     - influxdb-data:/var/lib/influxdb2
  #     - influxdb-config:/etc/influxdb2
  #   environment:
  #     - DOCKER_INFLUXDB_INIT_MODE=setup
  #     - DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_USERNAME:-admin}
  #     - DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_PASSWORD}
  #     - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN}
  #     - DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG:-network-optimizer}
  #     - DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET:-network_optimizer}
  #     - DOCKER_INFLUXDB_INIT_RETENTION=${INFLUXDB_RETENTION:-30d}
  #     - TZ=${TZ:-UTC}
  #   healthcheck:
  #     test: ["CMD", "influx", "ping"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 60s

  # grafana:
  #   image: grafana/grafana:latest
  #   container_name: network-optimizer-grafana
  #   restart: unless-stopped
  #   ports:
  #     - "3000:3000"
  #   volumes:
  #     - grafana-data:/var/lib/grafana
  #     - ./grafana/provisioning:/etc/grafana/provisioning:ro
  #     - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
  #   environment:
  #     - GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME:-admin}
  #     - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
  #     - GF_USERS_ALLOW_SIGN_UP=false
  #     - GF_AUTH_ANONYMOUS_ENABLED=true
  #     - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
  #     - TZ=${TZ:-UTC}
  #   depends_on:
  #     influxdb:
  #       condition: service_healthy
  #   healthcheck:
  #     test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 3
  #     start_period: 60s

# volumes:
#   influxdb-data:
#     driver: local
#   influxdb-config:
#     driver: local
#   grafana-data:
#     driver: local


================================================
FILE: docker/docker-compose.prod.yml
================================================
services:
  network-optimizer:
    image: ghcr.io/ozark-connect/network-optimizer:latest
    container_name: network-optimizer
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./data:/app/data              # SQLite, configs, license
      - ./ssh-keys:/app/ssh-keys      # Optional: SSH keys for agent deployment
      - ./logs:/app/logs              # Application logs
    environment:
      - TZ=${TZ:-America/Chicago}
      # Bind to localhost only (for reverse proxy) or all interfaces (direct access)
      - BIND_LOCALHOST_ONLY=${BIND_LOCALHOST_ONLY:-false}
      - APP_PASSWORD=${APP_PASSWORD:-}
      - DEMO_MODE_MAPPINGS=${DEMO_MODE_MAPPINGS:-}
      # Host IP/name for path analysis and CORS (required for client speed tests)
      - HOST_IP=${HOST_IP:-}
      - HOST_NAME=${HOST_NAME:-}
      # Reverse proxy hostname (e.g., optimizer.example.com) - overrides HOST_NAME for API URLs
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
      # OpenSpeedTest configuration (for UI display and CORS)
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # iperf3 server mode - enable to accept client-initiated speed tests on port 5201
      - Iperf3Server__Enabled=${IPERF3_SERVER_ENABLED:-false}
      # Logging (Trace, Debug, Information, Warning, Error, Critical)
      - Logging__LogLevel__Default=${LOG_LEVEL:-Information}
      - Logging__LogLevel__NetworkOptimizer=${APP_LOG_LEVEL:-Information}
      # Point to existing InfluxDB if desired (optional)
      # - INFLUXDB_URL=http://localhost:8086
      # - INFLUXDB_TOKEN=${INFLUXDB_TOKEN}
      # - INFLUXDB_ORG=${INFLUXDB_ORG:-network-optimizer}
      # - INFLUXDB_BUCKET=${INFLUXDB_BUCKET:-network_optimizer}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8042/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Network Optimizer Speed Test - customized OpenSpeedTest that sends results to Network Optimizer
  # Requires HOST_IP or HOST_NAME to be set in .env for result reporting
  network-optimizer-speedtest:
    image: ghcr.io/ozark-connect/speedtest:latest
    container_name: network-optimizer-speedtest
    restart: unless-stopped
    ports:
      - "${OPENSPEEDTEST_PORT:-3005}:3000"
    sysctls:
      # Raise TCP autotuning ceiling so long-RTT single-stream speedtests are not rwnd-bound.
      # Container has its own netns; without this the ceiling is the 6MB kernel default,
      # which caps single-stream throughput at ~rwnd/RTT (e.g. ~225 Mbps at 100ms RTT).
      net.ipv4.tcp_rmem: "4096 131072 33554432"
      net.ipv4.tcp_wmem: "4096 65536 33554432"
      net.ipv4.tcp_mtu_probing: "1"
      # tcp_congestion_control is intentionally not set here — it hard-fails
      # container start on kernels without the bbr module loaded (Synology, QNAP,
      # some Proxmox/LXC setups) and compose sysctls are all-or-nothing. The
      # entrypoint reports CC state and tells the operator how to enable bbr on
      # the host if desired.
    environment:
      - TZ=${TZ:-America/Chicago}
      # For URL construction and host enforcement redirect
      - HOST_NAME=${HOST_NAME:-}
      - HOST_IP=${HOST_IP:-}
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # For result reporting URL
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

# Production config - pre-built images from GHCR, host network mode
# For local dev with build from source, use docker-compose.yml


================================================
FILE: docker/docker-compose.yml
================================================
services:
  network-optimizer:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    image: ghcr.io/ozark-connect/network-optimizer:latest
    container_name: network-optimizer
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./data:/app/data              # SQLite, configs, license
      - ./ssh-keys:/app/ssh-keys      # Optional: SSH keys for agent deployment
      - ./logs:/app/logs              # Application logs
    environment:
      - TZ=${TZ:-America/Chicago}
      # Bind to localhost only (for reverse proxy) or all interfaces (direct access)
      - BIND_LOCALHOST_ONLY=${BIND_LOCALHOST_ONLY:-false}
      - APP_PASSWORD=${APP_PASSWORD:-}
      - DEMO_MODE_MAPPINGS=${DEMO_MODE_MAPPINGS:-}
      # Host IP/name for path analysis and CORS (required for client speed tests)
      - HOST_IP=${HOST_IP:-}
      - HOST_NAME=${HOST_NAME:-}
      # Reverse proxy hostname (e.g., optimizer.example.com) - overrides HOST_NAME for API URLs
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
      # OpenSpeedTest configuration (for UI display and CORS)
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # iperf3 server mode - enable to accept client-initiated speed tests on port 5201
      - Iperf3Server__Enabled=${IPERF3_SERVER_ENABLED:-false}
      # Logging (Trace, Debug, Information, Warning, Error, Critical)
      - Logging__LogLevel__Default=${LOG_LEVEL:-Information}
      - Logging__LogLevel__NetworkOptimizer=${APP_LOG_LEVEL:-Information}
      # Point to existing InfluxDB if desired (optional)
      # - INFLUXDB_URL=http://localhost:8086
      # - INFLUXDB_TOKEN=${INFLUXDB_TOKEN}
      # - INFLUXDB_ORG=${INFLUXDB_ORG:-network-optimizer}
      # - INFLUXDB_BUCKET=${INFLUXDB_BUCKET:-network_optimizer}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8042/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Network Optimizer Speed Test - customized OpenSpeedTest that sends results to Network Optimizer
  # Requires HOST_IP or HOST_NAME to be set in .env for result reporting
  network-optimizer-speedtest:
    build:
      context: ..
      dockerfile: docker/openspeedtest/Dockerfile
    image: ghcr.io/ozark-connect/speedtest:latest
    container_name: network-optimizer-speedtest
    restart: unless-stopped
    ports:
      - "${OPENSPEEDTEST_PORT:-3005}:3000"
    sysctls:
      # Raise TCP autotuning ceiling so long-RTT single-stream speedtests are not rwnd-bound.
      # Container has its own netns; without this the ceiling is the 6MB kernel default,
      # which caps single-stream throughput at ~rwnd/RTT (e.g. ~225 Mbps at 100ms RTT).
      net.ipv4.tcp_rmem: "4096 131072 33554432"
      net.ipv4.tcp_wmem: "4096 65536 33554432"
      net.ipv4.tcp_mtu_probing: "1"
      # tcp_congestion_control is intentionally not set here — it hard-fails
      # container start on kernels without the bbr module loaded (Synology, QNAP,
      # some Proxmox/LXC setups) and compose sysctls are all-or-nothing. The
      # entrypoint reports CC state and tells the operator how to enable bbr on
      # the host if desired.
    environment:
      - TZ=${TZ:-America/Chicago}
      # For URL construction and host enforcement redirect
      - HOST_NAME=${HOST_NAME:-}
      - HOST_IP=${HOST_IP:-}
      - OPENSPEEDTEST_PORT=${OPENSPEEDTEST_PORT:-3005}
      - OPENSPEEDTEST_HOST=${OPENSPEEDTEST_HOST:-}
      - OPENSPEEDTEST_HTTPS=${OPENSPEEDTEST_HTTPS:-false}
      - OPENSPEEDTEST_HTTPS_PORT=${OPENSPEEDTEST_HTTPS_PORT:-443}
      # For result reporting URL
      - REVERSE_PROXIED_HOST_NAME=${REVERSE_PROXIED_HOST_NAME:-}
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

# Production config - host network mode, localhost only (behind Caddy)
# For local dev with InfluxDB/Grafana, use docker-compose.local.yml


================================================
FILE: docker/entrypoint.sh
================================================
#!/bin/bash
set -e

# Set container timezone from TZ env var so .NET TimeZoneInfo.Local is correct
if [ -n "$TZ" ] && [ -f "/usr/share/zoneinfo/$TZ" ]; then
    ln -sf "/usr/share/zoneinfo/$TZ" /etc/localtime
    echo "$TZ" > /etc/timezone
fi

# Fix ownership of mounted volumes (they may be created as root by Docker)
# This runs as root before dropping to the app user
chown -R app:app /app/data /app/logs /app/ssh-keys 2>/dev/null || true

# Set bind address based on BIND_LOCALHOST_ONLY
# Default: false (bind to all interfaces for direct network access)
# Set to true when behind a reverse proxy on the same host
if [ "${BIND_LOCALHOST_ONLY,,}" = "true" ]; then
    export ASPNETCORE_URLS="http://127.0.0.1:8042"
    echo "Binding to localhost only (127.0.0.1:8042)"
else
    export ASPNETCORE_URLS="http://0.0.0.0:8042"
    echo "Binding to all interfaces (0.0.0.0:8042)"
fi

# Drop to app user and run the application
exec gosu app dotnet NetworkOptimizer.Web.dll "$@"


================================================
FILE: docker/grafana/dashboards/network-overview.json
================================================
{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "grafana",
          "uid": "-- Grafana --"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 1,
  "id": null,
  "links": [],
  "liveNow": false,
  "panels": [
    {
      "datasour
Download .txt
gitextract_r0774cwm/

├── .dockerignore
├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── Directory.Build.props
├── LICENSE
├── NetworkOptimizer.sln
├── README.md
├── TODO.md
├── docker/
│   ├── .dockerignore
│   ├── .env.example
│   ├── DEPLOYMENT.md
│   ├── Dockerfile
│   ├── NATIVE-DEPLOYMENT.md
│   ├── QUICK-REFERENCE.md
│   ├── README.md
│   ├── docker-compose.local.yml
│   ├── docker-compose.macos.yml
│   ├── docker-compose.prod.yml
│   ├── docker-compose.yml
│   ├── entrypoint.sh
│   ├── grafana/
│   │   ├── dashboards/
│   │   │   ├── network-overview.json
│   │   │   ├── security-posture.json
│   │   │   ├── sqm-performance.json
│   │   │   └── switch-deep-dive.json
│   │   └── provisioning/
│   │       ├── dashboards/
│   │       │   └── dashboards.yml
│   │       └── datasources/
│   │           └── influxdb.yml
│   └── openspeedtest/
│       ├── Dockerfile
│       ├── entrypoint.sh
│       └── nginx.conf
├── docs/
│   ├── MACOS-INSTALLATION.md
│   ├── PLAN-unifi-api-abstraction.md
│   └── features/
│       └── speed-test-roadmap.md
├── nuget.config
├── packages/
│   ├── Blazor-ApexCharts.6.1.1-ozarkconnect.1.nupkg
│   └── Blazor-ApexCharts.6.1.1-ozarkconnect.2.nupkg
├── renovate.json
├── scripts/
│   ├── README.md
│   ├── build-installer.ps1
│   ├── build.sh
│   ├── clean.sh
│   ├── coverage.runsettings
│   ├── coverage.sh
│   ├── deploy-external-speedtest.sh
│   ├── docker-build.sh
│   ├── docker-run.sh
│   ├── docker-stop.sh
│   ├── extract-elevation0-from-images.py
│   ├── install-macos-native.sh
│   ├── parse-antenna-patterns.ps1
│   ├── proxmox/
│   │   ├── README.md
│   │   └── install.sh
│   ├── publish.sh
│   ├── reset-password.ps1
│   ├── reset-password.sh
│   ├── sync-perf-tweaks.ps1
│   ├── test.sh
│   └── watch.sh
├── src/
│   ├── NetworkOptimizer.Agents/
│   │   ├── .gitignore
│   │   ├── AgentDeployer.cs
│   │   ├── AgentHealthMonitor.cs
│   │   ├── Models/
│   │   │   ├── AgentConfiguration.cs
│   │   │   ├── DeploymentResult.cs
│   │   │   └── SshCredentials.cs
│   │   ├── NetworkOptimizer.Agents.csproj
│   │   ├── README.md
│   │   ├── ScriptRenderer.cs
│   │   └── Templates/
│   │       ├── install-linux.sh.template
│   │       ├── linux-agent.service.template
│   │       └── linux-agent.sh.template
│   ├── NetworkOptimizer.Alerts/
│   │   ├── AlertCooldownTracker.cs
│   │   ├── AlertCorrelationService.cs
│   │   ├── AlertProcessingService.cs
│   │   ├── AlertRuleEvaluator.cs
│   │   ├── DefaultAlertRules.cs
│   │   ├── Delivery/
│   │   │   ├── DiscordChannelConfig.cs
│   │   │   ├── DiscordDeliveryChannel.cs
│   │   │   ├── EmailChannelConfig.cs
│   │   │   ├── EmailDeliveryChannel.cs
│   │   │   ├── IAlertDeliveryChannel.cs
│   │   │   ├── ISecretDecryptor.cs
│   │   │   ├── NtfyChannelConfig.cs
│   │   │   ├── NtfyDeliveryChannel.cs
│   │   │   ├── SlackChannelConfig.cs
│   │   │   ├── SlackDeliveryChannel.cs
│   │   │   ├── TeamsChannelConfig.cs
│   │   │   ├── TeamsDeliveryChannel.cs
│   │   │   ├── TimestampFormatter.cs
│   │   │   ├── WebhookChannelConfig.cs
│   │   │   └── WebhookDeliveryChannel.cs
│   │   ├── DigestService.cs
│   │   ├── Events/
│   │   │   ├── AlertEvent.cs
│   │   │   ├── AlertEventBus.cs
│   │   │   └── IAlertEventBus.cs
│   │   ├── Interfaces/
│   │   │   ├── IAlertRepository.cs
│   │   │   ├── IDigestStateStore.cs
│   │   │   └── IScheduleRepository.cs
│   │   ├── Models/
│   │   │   ├── AlertHistoryEntry.cs
│   │   │   ├── AlertIncident.cs
│   │   │   ├── AlertRule.cs
│   │   │   ├── DeliveryChannel.cs
│   │   │   └── ScheduledTask.cs
│   │   ├── NetworkOptimizer.Alerts.csproj
│   │   ├── ScheduleService.cs
│   │   └── Templates/
│   │       ├── alert-email.html
│   │       └── digest-email.html
│   ├── NetworkOptimizer.Audit/
│   │   ├── Analyzers/
│   │   │   ├── AuditScorer.cs
│   │   │   ├── FirewallGroupHelper.cs
│   │   │   ├── FirewallRuleAnalyzer.cs
│   │   │   ├── FirewallRuleEvaluator.cs
│   │   │   ├── FirewallRuleOverlapDetector.cs
│   │   │   ├── FirewallRuleParser.cs
│   │   │   ├── HttpAppIds.cs
│   │   │   ├── PortSecurityAnalyzer.cs
│   │   │   ├── UpnpSecurityAnalyzer.cs
│   │   │   └── VlanAnalyzer.cs
│   │   ├── CHANGELOG.md
│   │   ├── ConfigAuditEngine.cs
│   │   ├── Constants/
│   │   │   └── DetectionConstants.cs
│   │   ├── DeviceNameHints.cs
│   │   ├── Dns/
│   │   │   ├── DnatDnsAnalyzer.cs
│   │   │   ├── DnsAppIds.cs
│   │   │   ├── DnsSecurityAnalyzer.cs
│   │   │   ├── DnsStampDecoder.cs
│   │   │   ├── DohProviderRegistry.cs
│   │   │   └── ThirdPartyDnsDetector.cs
│   │   ├── IssueTypes.cs
│   │   ├── Models/
│   │   │   ├── AuditIssue.cs
│   │   │   ├── AuditRequest.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── AuditSeverity.cs
│   │   │   ├── DeviceAllowanceSettings.cs
│   │   │   ├── DeviceDetectionResult.cs
│   │   │   ├── FirewallAction.cs
│   │   │   ├── FirewallRule.cs
│   │   │   ├── NetworkInfo.cs
│   │   │   ├── OfflineClientInfo.cs
│   │   │   ├── PortInfo.cs
│   │   │   ├── SwitchInfo.cs
│   │   │   └── WirelessClientInfo.cs
│   │   ├── NetworkOptimizer.Audit.csproj
│   │   ├── README.md
│   │   ├── Rules/
│   │   │   ├── AccessPortVlanRule.cs
│   │   │   ├── CameraVlanRule.cs
│   │   │   ├── FirewallAnyAnyRule.cs
│   │   │   ├── IAuditRule.cs
│   │   │   ├── IWirelessAuditRule.cs
│   │   │   ├── IotVlanRule.cs
│   │   │   ├── MacRestrictionRule.cs
│   │   │   ├── PortIsolationRule.cs
│   │   │   ├── PortNameHelper.cs
│   │   │   ├── UnusedPortRule.cs
│   │   │   ├── VlanPlacementChecker.cs
│   │   │   ├── VlanSubnetMismatchRule.cs
│   │   │   ├── WiredSubnetMismatchRule.cs
│   │   │   ├── WirelessCameraVlanRule.cs
│   │   │   └── WirelessIotVlanRule.cs
│   │   ├── Scoring/
│   │   │   └── ScoreConstants.cs
│   │   └── Services/
│   │       ├── Detectors/
│   │       │   ├── FingerprintDetector.cs
│   │       │   ├── MacOuiDetector.cs
│   │       │   └── NamePatternDetector.cs
│   │       ├── DeviceTypeDetectionService.cs
│   │       ├── FirewallZoneLookup.cs
│   │       ├── IIeeeOuiDatabase.cs
│   │       └── IeeeOuiDatabase.cs
│   ├── NetworkOptimizer.Core/
│   │   ├── Caching/
│   │   │   └── AsyncCachedValue.cs
│   │   ├── Enums/
│   │   │   ├── AgentType.cs
│   │   │   ├── AlertSeverity.cs
│   │   │   ├── AlertStatus.cs
│   │   │   ├── AuditSeverity.cs
│   │   │   ├── ClientDeviceCategory.cs
│   │   │   ├── DeviceType.cs
│   │   │   └── MeasurementType.cs
│   │   ├── Extensions/
│   │   │   └── ServiceProviderExtensions.cs
│   │   ├── FeatureFlags.cs
│   │   ├── Helpers/
│   │   │   ├── CloudflareIpRanges.cs
│   │   │   ├── DisplayFormatters.cs
│   │   │   ├── JsonExtensions.cs
│   │   │   ├── NetworkFormatHelpers.cs
│   │   │   ├── NetworkUtilities.cs
│   │   │   └── ProcessUtilities.cs
│   │   ├── Interfaces/
│   │   │   ├── IAgentDeployer.cs
│   │   │   ├── IAuditEngine.cs
│   │   │   ├── IMetricsStorage.cs
│   │   │   ├── IReportGenerator.cs
│   │   │   ├── ISqmManager.cs
│   │   │   └── IUniFiApiClient.cs
│   │   ├── Models/
│   │   │   ├── AgentStatus.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── NetworkConfiguration.cs
│   │   │   ├── ProtectCamera.cs
│   │   │   ├── SqmConfiguration.cs
│   │   │   └── UniFiDevice.cs
│   │   ├── NetworkOptimizer.Core.csproj
│   │   └── VendorSpecificAttribute.cs
│   ├── NetworkOptimizer.Diagnostics/
│   │   ├── Analyzers/
│   │   │   ├── ApLockAnalyzer.cs
│   │   │   ├── PerformanceAnalyzer.cs
│   │   │   ├── PortProfile8021xAnalyzer.cs
│   │   │   ├── PortProfileSuggestionAnalyzer.cs
│   │   │   ├── StreamingAppIds.cs
│   │   │   └── TrunkConsistencyAnalyzer.cs
│   │   ├── DiagnosticsEngine.cs
│   │   ├── Models/
│   │   │   ├── AccessPortVlanIssue.cs
│   │   │   ├── ApLockIssue.cs
│   │   │   ├── DiagnosticSeverity.cs
│   │   │   ├── DiagnosticsResult.cs
│   │   │   ├── PerformanceIssue.cs
│   │   │   ├── PortProfile8021xIssue.cs
│   │   │   ├── PortProfileSuggestion.cs
│   │   │   └── TrunkConsistencyIssue.cs
│   │   └── NetworkOptimizer.Diagnostics.csproj
│   ├── NetworkOptimizer.Installer/
│   │   ├── CustomStrings.wxl
│   │   ├── Iperf3/
│   │   │   ├── .gitignore
│   │   │   └── Download-Iperf3.ps1
│   │   ├── Iperf3Component.wxs
│   │   ├── LICENSE.txt
│   │   ├── License.rtf
│   │   ├── NetworkOptimizer.Installer.wixproj
│   │   ├── Package.wxs
│   │   ├── ServiceComponent.wxs
│   │   ├── SpeedTest/
│   │   │   ├── .gitignore
│   │   │   ├── Download-Nginx.ps1
│   │   │   ├── Start-SpeedTest.ps1
│   │   │   ├── config.js.template
│   │   │   └── nginx.conf
│   │   ├── SpeedTestComponent.wxs
│   │   ├── Traefik/
│   │   │   ├── .gitignore
│   │   │   └── Download-Traefik.ps1
│   │   └── TraefikComponent.wxs
│   ├── NetworkOptimizer.Monitoring/
│   │   ├── AlertEngine.cs
│   │   ├── MetricsAggregator.cs
│   │   ├── Models/
│   │   │   ├── Alert.cs
│   │   │   ├── AlertThreshold.cs
│   │   │   ├── CellularModemStats.cs
│   │   │   ├── DeviceMetrics.cs
│   │   │   └── InterfaceMetrics.cs
│   │   ├── NetworkOptimizer.Monitoring.csproj
│   │   ├── QmicliParser.cs
│   │   ├── README.md
│   │   ├── SnmpConfiguration.cs
│   │   ├── SnmpPoller.cs
│   │   └── UniFiOids.cs
│   ├── NetworkOptimizer.Reports/
│   │   ├── BrandingOptions.cs
│   │   ├── INDEX.md
│   │   ├── MarkdownReportGenerator.cs
│   │   ├── NetworkOptimizer.Reports.csproj
│   │   ├── PdfReportGenerator.cs
│   │   ├── README.md
│   │   ├── ReportData.cs
│   │   └── Templates/
│   │       └── .gitkeep
│   ├── NetworkOptimizer.Sqm/
│   │   ├── ARCHITECTURE.md
│   │   ├── BaselineCalculator.cs
│   │   ├── InputSanitizer.cs
│   │   ├── LatencyMonitor.cs
│   │   ├── Models/
│   │   │   ├── BaselineData.cs
│   │   │   ├── ConnectionProfile.cs
│   │   │   ├── SpeedtestResult.cs
│   │   │   ├── SqmConfiguration.cs
│   │   │   └── SqmStatus.cs
│   │   ├── NetworkOptimizer.Sqm.csproj
│   │   ├── README.md
│   │   ├── ScriptGenerator.cs
│   │   ├── SpeedtestIntegration.cs
│   │   └── SqmManager.cs
│   ├── NetworkOptimizer.Storage/
│   │   ├── .gitignore
│   │   ├── Helpers/
│   │   │   └── SpeedTestFilterHelper.cs
│   │   ├── InfluxDbStorage.cs
│   │   ├── Interfaces/
│   │   │   ├── IAgentRepository.cs
│   │   │   ├── IAuditRepository.cs
│   │   │   ├── IMetricsStorage.cs
│   │   │   ├── IModemRepository.cs
│   │   │   ├── ISettingsRepository.cs
│   │   │   ├── ISpeedTestRepository.cs
│   │   │   ├── ISqmRepository.cs
│   │   │   └── IUniFiRepository.cs
│   │   ├── Migrations/
│   │   │   ├── 20251208000000_InitialCreate.Designer.cs
│   │   │   ├── 20251208000000_InitialCreate.cs
│   │   │   ├── 20251210000000_AddModemAndSpeedTables.Designer.cs
│   │   │   ├── 20251210000000_AddModemAndSpeedTables.cs
│   │   │   ├── 20251216000000_AddUniFiSshSettings.Designer.cs
│   │   │   ├── 20251216000000_AddUniFiSshSettings.cs
│   │   │   ├── 20251217000000_AddDismissedIssues.Designer.cs
│   │   │   ├── 20251217000000_AddDismissedIssues.cs
│   │   │   ├── 20251217100000_AddGatewaySshSettings.Designer.cs
│   │   │   ├── 20251217100000_AddGatewaySshSettings.cs
│   │   │   ├── 20251217200000_AddStartIperf3ServerToDeviceConfig.Designer.cs
│   │   │   ├── 20251217200000_AddStartIperf3ServerToDeviceConfig.cs
│   │   │   ├── 20251217300000_AddSystemSettings.Designer.cs
│   │   │   ├── 20251217300000_AddSystemSettings.cs
│   │   │   ├── 20251218000000_AddSshCredentialOverridesToDeviceConfig.Designer.cs
│   │   │   ├── 20251218000000_AddSshCredentialOverridesToDeviceConfig.cs
│   │   │   ├── 20251219000000_AddUniFiConnectionSettings.Designer.cs
│   │   │   ├── 20251219000000_AddUniFiConnectionSettings.cs
│   │   │   ├── 20251224000000_AddPathAnalysisJson.Designer.cs
│   │   │   ├── 20251224000000_AddPathAnalysisJson.cs
│   │   │   ├── 20251227000000_AddTcMonitorPort.Designer.cs
│   │   │   ├── 20251227000000_AddTcMonitorPort.cs
│   │   │   ├── 20251227100000_AddSqmWanConfiguration.Designer.cs
│   │   │   ├── 20251227100000_AddSqmWanConfiguration.cs
│   │   │   ├── 20251228000000_AddAdminSettings.Designer.cs
│   │   │   ├── 20251228000000_AddAdminSettings.cs
│   │   │   ├── 20251228100000_AddSqmSpeedtestSchedule.Designer.cs
│   │   │   ├── 20251228100000_AddSqmSpeedtestSchedule.cs
│   │   │   ├── 20251229000000_AddReportDataJson.Designer.cs
│   │   │   ├── 20251229000000_AddReportDataJson.cs
│   │   │   ├── 20260102000000_AddLocalIpToIperf3Result.Designer.cs
│   │   │   ├── 20260102000000_AddLocalIpToIperf3Result.cs
│   │   │   ├── 20260103000000_AddIgnoreControllerSSLErrors.Designer.cs
│   │   │   ├── 20260103000000_AddIgnoreControllerSSLErrors.cs
│   │   │   ├── 20260104100000_AddClientSpeedTestFieldsToIperf3Result.Designer.cs
│   │   │   ├── 20260104100000_AddClientSpeedTestFieldsToIperf3Result.cs
│   │   │   ├── 20260106000000_AddLocationAndWifiSignal.Designer.cs
│   │   │   ├── 20260106000000_AddLocationAndWifiSignal.cs
│   │   │   ├── 20260107000000_AddWifiRadio.Designer.cs
│   │   │   ├── 20260107000000_AddWifiRadio.cs
│   │   │   ├── 20260107100000_AddWifiMlo.Designer.cs
│   │   │   ├── 20260107100000_AddWifiMlo.cs
│   │   │   ├── 20260107200000_AddWifiTxRxRates.Designer.cs
│   │   │   ├── 20260107200000_AddWifiTxRxRates.cs
│   │   │   ├── 20260110000000_AddIperf3BinaryPathToDeviceConfig.Designer.cs
│   │   │   ├── 20260110000000_AddIperf3BinaryPathToDeviceConfig.cs
│   │   │   ├── 20260113000000_AddUpnpNotes.Designer.cs
│   │   │   ├── 20260113000000_AddUpnpNotes.cs
│   │   │   ├── 20260124000000_AddNotesToIperf3Result.Designer.cs
│   │   │   ├── 20260124000000_AddNotesToIperf3Result.cs
│   │   │   ├── 20260209200000_AddApLocations.Designer.cs
│   │   │   ├── 20260209200000_AddApLocations.cs
│   │   │   ├── 20260210100000_AddLoadedLatencyColumns.Designer.cs
│   │   │   ├── 20260210100000_AddLoadedLatencyColumns.cs
│   │   │   ├── 20260211000000_AddWanIdentityColumns.Designer.cs
│   │   │   ├── 20260211000000_AddWanIdentityColumns.cs
│   │   │   ├── 20260211200000_AddBuildingsAndFloorPlans.Designer.cs
│   │   │   ├── 20260211200000_AddBuildingsAndFloorPlans.cs
│   │   │   ├── 20260211300000_AddApOrientationDeg.Designer.cs
│   │   │   ├── 20260211300000_AddApOrientationDeg.cs
│   │   │   ├── 20260211400000_AddApMountType.Designer.cs
│   │   │   ├── 20260211400000_AddApMountType.cs
│   │   │   ├── 20260212000000_AddFloorMaterial.Designer.cs
│   │   │   ├── 20260212000000_AddFloorMaterial.cs
│   │   │   ├── 20260213000000_AddClientSignalLog.Designer.cs
│   │   │   ├── 20260213000000_AddClientSignalLog.cs
│   │   │   ├── 20260213000000_AddPlannedAps.Designer.cs
│   │   │   ├── 20260213000000_AddPlannedAps.cs
│   │   │   ├── 20260214100000_AddPerBandTxPower.Designer.cs
│   │   │   ├── 20260214100000_AddPerBandTxPower.cs
│   │   │   ├── 20260220000000_AddFloorPlanImages.Designer.cs
│   │   │   ├── 20260220000000_AddFloorPlanImages.cs
│   │   │   ├── 20260221000000_AddAlertTables.Designer.cs
│   │   │   ├── 20260221000000_AddAlertTables.cs
│   │   │   ├── 20260221100000_AddThreatTables.Designer.cs
│   │   │   ├── 20260221100000_AddThreatTables.cs
│   │   │   ├── 20260222100000_AddTrafficFlowFields.Designer.cs
│   │   │   ├── 20260222100000_AddTrafficFlowFields.cs
│   │   │   ├── 20260222200000_AddThreatNoiseFilters.Designer.cs
│   │   │   ├── 20260222200000_AddThreatNoiseFilters.cs
│   │   │   ├── 20260223000000_AddScheduledTasks.Designer.cs
│   │   │   ├── 20260223000000_AddScheduledTasks.cs
│   │   │   ├── 20260223100000_AddAlertRuleThreshold.Designer.cs
│   │   │   ├── 20260223100000_AddAlertRuleThreshold.cs
│   │   │   ├── 20260225200000_AddPatternLastAlertedAt.Designer.cs
│   │   │   ├── 20260225200000_AddPatternLastAlertedAt.cs
│   │   │   ├── 20260226010000_AddPatternDedupKey.Designer.cs
│   │   │   ├── 20260226010000_AddPatternDedupKey.cs
│   │   │   ├── 20260226100000_AddAuditIsScheduled.Designer.cs
│   │   │   ├── 20260226100000_AddAuditIsScheduled.cs
│   │   │   ├── 20260226120000_AddSqmBaselineLatency.Designer.cs
│   │   │   ├── 20260226120000_AddSqmBaselineLatency.cs
│   │   │   ├── 20260228000000_AddWanDataUsageTables.Designer.cs
│   │   │   ├── 20260228000000_AddWanDataUsageTables.cs
│   │   │   ├── 20260301000000_AddSignalLogChannelWidth.Designer.cs
│   │   │   ├── 20260301000000_AddSignalLogChannelWidth.cs
│   │   │   ├── 20260301200000_AddSnapshotGatewayBootTime.Designer.cs
│   │   │   ├── 20260301200000_AddSnapshotGatewayBootTime.cs
│   │   │   ├── 20260306000000_AddAlertSourceUrl.Designer.cs
│   │   │   ├── 20260306000000_AddAlertSourceUrl.cs
│   │   │   ├── 20260311000000_AddDeviceIperf3Overrides.Designer.cs
│   │   │   ├── 20260311000000_AddDeviceIperf3Overrides.cs
│   │   │   ├── 20260312000000_PurgeStaleCrowdSecNegativeCache.Designer.cs
│   │   │   ├── 20260312000000_PurgeStaleCrowdSecNegativeCache.cs
│   │   │   ├── 20260318000000_AddExternalServerName.Designer.cs
│   │   │   ├── 20260318000000_AddExternalServerName.cs
│   │   │   ├── 20260320000000_AddWanSteerTrafficClasses.Designer.cs
│   │   │   ├── 20260320000000_AddWanSteerTrafficClasses.cs
│   │   │   ├── 20260402100000_AddCongestionSeverity.Designer.cs
│   │   │   ├── 20260402100000_AddCongestionSeverity.cs
│   │   │   ├── 20260404000000_AddApiKey.Designer.cs
│   │   │   ├── 20260404000000_AddApiKey.cs
│   │   │   ├── 20260405000000_AddExternalSpeedTestServers.Designer.cs
│   │   │   ├── 20260405000000_AddExternalSpeedTestServers.cs
│   │   │   ├── 20260428000000_AddSqmLinkSpeedOverride.Designer.cs
│   │   │   ├── 20260428000000_AddSqmLinkSpeedOverride.cs
│   │   │   ├── 20260505000000_AddSqmBootDelay.Designer.cs
│   │   │   ├── 20260505000000_AddSqmBootDelay.cs
│   │   │   ├── 20260507000000_AddPerfTweakSettings.Designer.cs
│   │   │   ├── 20260507000000_AddPerfTweakSettings.cs
│   │   │   └── NetworkOptimizerDbContextModelSnapshot.cs
│   │   ├── Models/
│   │   │   ├── AdminSettings.cs
│   │   │   ├── AgentConfiguration.cs
│   │   │   ├── ApLocation.cs
│   │   │   ├── AuditResult.cs
│   │   │   ├── Building.cs
│   │   │   ├── ClientSignalLog.cs
│   │   │   ├── DeviceSshConfiguration.cs
│   │   │   ├── DismissedIssue.cs
│   │   │   ├── ExternalSpeedTestServer.cs
│   │   │   ├── FloorPlan.cs
│   │   │   ├── FloorPlanImage.cs
│   │   │   ├── GatewaySshSettings.cs
│   │   │   ├── Iperf3Result.cs
│   │   │   ├── LicenseInfo.cs
│   │   │   ├── ModemConfiguration.cs
│   │   │   ├── NetworkOptimizerDbContext.cs
│   │   │   ├── PerfTweakSetting.cs
│   │   │   ├── PlannedAp.cs
│   │   │   ├── SqmBaseline.cs
│   │   │   ├── SqmWanConfiguration.cs
│   │   │   ├── SystemSetting.cs
│   │   │   ├── UniFiConnectionSettings.cs
│   │   │   ├── UniFiSshSettings.cs
│   │   │   ├── UpnpNote.cs
│   │   │   ├── WanDataUsageConfig.cs
│   │   │   ├── WanDataUsageSnapshot.cs
│   │   │   └── WanSteerTrafficClass.cs
│   │   ├── NetworkOptimizer.Storage.csproj
│   │   ├── README.md
│   │   ├── Repositories/
│   │   │   ├── AgentRepository.cs
│   │   │   ├── AlertRepository.cs
│   │   │   ├── AuditRepository.cs
│   │   │   ├── ModemRepository.cs
│   │   │   ├── ScheduleRepository.cs
│   │   │   ├── SettingsRepository.cs
│   │   │   ├── SpeedTestRepository.cs
│   │   │   ├── SqmRepository.cs
│   │   │   ├── ThreatRepository.cs
│   │   │   └── UniFiRepository.cs
│   │   ├── RepositoryBase.cs
│   │   ├── Services/
│   │   │   ├── CredentialProtectionService.cs
│   │   │   └── ICredentialProtectionService.cs
│   │   ├── StorageConfiguration.cs
│   │   └── StorageServiceExtensions.cs
│   ├── NetworkOptimizer.Threats/
│   │   ├── Analysis/
│   │   │   ├── BruteForceDetector.cs
│   │   │   ├── DDoSDetector.cs
│   │   │   ├── ExploitCampaignDetector.cs
│   │   │   ├── ExposureValidator.cs
│   │   │   ├── FlowInterestFilter.cs
│   │   │   ├── KillChainClassifier.cs
│   │   │   ├── ScanSweepDetector.cs
│   │   │   └── ThreatPatternAnalyzer.cs
│   │   ├── CrowdSec/
│   │   │   ├── CrowdSecClient.cs
│   │   │   ├── CrowdSecEnrichmentService.cs
│   │   │   └── CrowdSecModels.cs
│   │   ├── Enrichment/
│   │   │   └── GeoEnrichmentService.cs
│   │   ├── Interfaces/
│   │   │   ├── IThreatRepository.cs
│   │   │   ├── IThreatSettingsAccessor.cs
│   │   │   └── IUniFiClientAccessor.cs
│   │   ├── Models/
│   │   │   ├── CrowdSecReputation.cs
│   │   │   ├── EventSource.cs
│   │   │   ├── ExposureReport.cs
│   │   │   ├── GeoInfo.cs
│   │   │   ├── KillChainStage.cs
│   │   │   ├── PatternType.cs
│   │   │   ├── ThreatAction.cs
│   │   │   ├── ThreatEvent.cs
│   │   │   ├── ThreatNoiseFilter.cs
│   │   │   └── ThreatPattern.cs
│   │   ├── NetworkOptimizer.Threats.csproj
│   │   ├── ThreatCollectionService.cs
│   │   └── ThreatEventNormalizer.cs
│   ├── NetworkOptimizer.UniFi/
│   │   ├── ClientIpEnricher.cs
│   │   ├── Helpers/
│   │   │   ├── GlobalSwitchSettings.cs
│   │   │   └── VlanAnalysisHelper.cs
│   │   ├── Models/
│   │   │   ├── NetworkHop.cs
│   │   │   ├── NetworkPath.cs
│   │   │   ├── PathAnalysisResult.cs
│   │   │   ├── UniFiApiResponse.cs
│   │   │   ├── UniFiClientDetailResponse.cs
│   │   │   ├── UniFiClientResponse.cs
│   │   │   ├── UniFiDeviceResponse.cs
│   │   │   ├── UniFiFingerprintDatabase.cs
│   │   │   ├── UniFiFirewallGroup.cs
│   │   │   ├── UniFiFirewallRule.cs
│   │   │   ├── UniFiFirewallZone.cs
│   │   │   ├── UniFiIpsEvent.cs
│   │   │   ├── UniFiNetworkConfig.cs
│   │   │   ├── UniFiPortForwardRule.cs
│   │   │   ├── UniFiPortProfile.cs
│   │   │   ├── UniFiProtectDeviceResponse.cs
│   │   │   ├── UniFiSysInfo.cs
│   │   │   ├── UniFiThreatLogEntry.cs
│   │   │   ├── UniFiWlanConfig.cs
│   │   │   ├── WiFiManClientResponse.cs
│   │   │   └── WirelessRateSnapshot.cs
│   │   ├── NetworkOptimizer.UniFi.csproj
│   │   ├── NetworkPathAnalyzer.cs
│   │   ├── README.md
│   │   ├── RadioFormatHelper.cs
│   │   ├── UniFiApiClient.cs
│   │   ├── UniFiDiscovery.cs
│   │   └── UniFiProductDatabase.cs
│   ├── NetworkOptimizer.Web/
│   │   ├── App.razor
│   │   ├── Components/
│   │   │   ├── Layout/
│   │   │   │   ├── AuthLayout.razor
│   │   │   │   ├── MainLayout.razor
│   │   │   │   └── NavMenu.razor
│   │   │   ├── Pages/
│   │   │   │   ├── Agents.razor
│   │   │   │   ├── Alerts.razor
│   │   │   │   ├── Audit.razor
│   │   │   │   ├── ClientDashboard.razor
│   │   │   │   ├── ClientSpeedTest.razor
│   │   │   │   ├── ClientWanSpeedTest.razor
│   │   │   │   ├── Dashboard.razor
│   │   │   │   ├── Login.razor
│   │   │   │   ├── Optimize.razor
│   │   │   │   ├── PerformanceTweaks.razor
│   │   │   │   ├── PwaInstall.razor
│   │   │   │   ├── Settings.razor
│   │   │   │   ├── SpeedTest.razor
│   │   │   │   ├── Sqm.razor
│   │   │   │   ├── ThreatDashboard.razor
│   │   │   │   ├── UpnpInspector.razor
│   │   │   │   ├── WanSpeedTest.razor
│   │   │   │   ├── WanSteering.razor
│   │   │   │   └── WiFiOptimizer.razor
│   │   │   ├── Routes.razor
│   │   │   ├── ScrollRestoration.razor
│   │   │   ├── Shared/
│   │   │   │   ├── AgentStatusTable.razor
│   │   │   │   ├── AlertsList.razor
│   │   │   │   ├── CellularStatsPanel.razor
│   │   │   │   ├── DeviceCard.razor
│   │   │   │   ├── DeviceIcon.razor
│   │   │   │   ├── IssuesList.razor
│   │   │   │   ├── PwaBanner.razor
│   │   │   │   ├── SecurityScoreGauge.razor
│   │   │   │   ├── SpeedTestDetails.razor
│   │   │   │   ├── SpeedTestMap.razor
│   │   │   │   ├── SpeedTestSearchFilter.razor
│   │   │   │   ├── SponsorshipBanner.razor
│   │   │   │   ├── SqmStatusPanel.razor
│   │   │   │   ├── SshTroubleshootingTooltip.razor
│   │   │   │   ├── UpdateChecker.razor
│   │   │   │   ├── WanOption.cs
│   │   │   │   └── WiFi/
│   │   │   │       ├── AirtimeFairness.razor
│   │   │   │       ├── ApLoadBalance.razor
│   │   │   │       ├── BandSteeringAnalysis.razor
│   │   │   │       ├── ChannelAnalysis.razor
│   │   │   │       ├── ClientTimeline.razor
│   │   │   │       ├── ConnectivityFlow.razor
│   │   │   │       ├── EnvironmentalCorrelation.razor
│   │   │   │       ├── FloorPlanEditor.razor
│   │   │   │       ├── HealthScoreGauge.razor
│   │   │   │       ├── Metrics.razor
│   │   │   │       ├── PowerCoverageAnalysis.razor
│   │   │   │       ├── RoamingAnalytics.razor
│   │   │   │       ├── SpectrumAnalysis.razor
│   │   │   │       └── WiFiDashboardPanel.razor
│   │   │   └── _Imports.razor
│   │   ├── Endpoints/
│   │   │   ├── AlertEndpoints.cs
│   │   │   ├── EndpointHelpers.cs
│   │   │   └── SpeedTestEndpoints.cs
│   │   ├── Models/
│   │   │   ├── ApMapMarker.cs
│   │   │   └── ClientDashboardModels.cs
│   │   ├── NetworkOptimizer.Web.csproj
│   │   ├── Program.cs
│   │   ├── Properties/
│   │   │   └── launchSettings.json
│   │   ├── README.md
│   │   ├── Resources/
│   │   │   └── PerfTweaks/
│   │   │       ├── 06-mongodb-ssd-offload.sh
│   │   │       ├── 07-mongodb-ssd-backup.sh
│   │   │       ├── 10-journald-volatile.sh
│   │   │       ├── 15-fan-control-tuning.sh
│   │   │       ├── 20-sfp-sgmiiplus.sh
│   │   │       └── force_uniphy1_sgmiiplus.ko
│   │   ├── Services/
│   │   │   ├── AdminAuthService.cs
│   │   │   ├── AgentService.cs
│   │   │   ├── ApMapService.cs
│   │   │   ├── AuditService.cs
│   │   │   ├── CellularModemService.cs
│   │   │   ├── ClientDashboardService.cs
│   │   │   ├── ClientSpeedTestService.cs
│   │   │   ├── CloudflareSpeedTestService.cs
│   │   │   ├── ConfigTransferService.cs
│   │   │   ├── DashboardLayoutService.cs
│   │   │   ├── DashboardService.cs
│   │   │   ├── DiagnosticsService.cs
│   │   │   ├── FileVersionProvider.cs
│   │   │   ├── FingerprintDatabaseService.cs
│   │   │   ├── FloorPlanService.cs
│   │   │   ├── GatewaySpeedTestService.cs
│   │   │   ├── GatewayWanSpeedTestService.cs
│   │   │   ├── HeatmapDataCache.cs
│   │   │   ├── IAgentService.cs
│   │   │   ├── ICellularModemService.cs
│   │   │   ├── IDashboardService.cs
│   │   │   ├── IFingerprintDatabaseService.cs
│   │   │   ├── IGatewaySpeedTestService.cs
│   │   │   ├── IIperf3SpeedTestService.cs
│   │   │   ├── ISponsorshipService.cs
│   │   │   ├── ISqmDeploymentService.cs
│   │   │   ├── ISqmService.cs
│   │   │   ├── ISystemSettingsService.cs
│   │   │   ├── ITcMonitorClient.cs
│   │   │   ├── IUniFiSshService.cs
│   │   │   ├── Iperf3JsonParser.cs
│   │   │   ├── Iperf3ServerService.cs
│   │   │   ├── Iperf3SpeedTestService.cs
│   │   │   ├── JwtService.cs
│   │   │   ├── NginxHostedService.cs
│   │   │   ├── PasswordHasher.cs
│   │   │   ├── PdfStorageService.cs
│   │   │   ├── PerfTweaksDeploymentService.cs
│   │   │   ├── PlannedApService.cs
│   │   │   ├── PullToRefreshState.cs
│   │   │   ├── ScheduleExecutorRegistration.cs
│   │   │   ├── SponsorshipService.cs
│   │   │   ├── SqmDeploymentService.cs
│   │   │   ├── SqmService.cs
│   │   │   ├── Ssh/
│   │   │   │   ├── GatewaySshService.cs
│   │   │   │   ├── IGatewaySshService.cs
│   │   │   │   ├── SshClientService.cs
│   │   │   │   ├── SshCommandResult.cs
│   │   │   │   └── SshConnectionInfo.cs
│   │   │   ├── SystemSettingsService.cs
│   │   │   ├── TcMonitorClient.cs
│   │   │   ├── ThreatDashboardService.cs
│   │   │   ├── ThreatSettingsAccessor.cs
│   │   │   ├── TimeFormatHelper.cs
│   │   │   ├── TopologySnapshotService.cs
│   │   │   ├── TraefikHostedService.cs
│   │   │   ├── UniFiClientAccessor.cs
│   │   │   ├── UniFiConnectionService.cs
│   │   │   ├── UniFiSshService.cs
│   │   │   ├── UwnSpeedTestService.cs
│   │   │   ├── WanDataUsageService.cs
│   │   │   ├── WanSpeedTestServiceBase.cs
│   │   │   ├── WanSteerDeploymentService.cs
│   │   │   ├── WanSteerValidation.cs
│   │   │   └── WiFiOptimizerService.cs
│   │   ├── appsettings.Development.json
│   │   ├── appsettings.json
│   │   └── wwwroot/
│   │       ├── css/
│   │       │   └── app.css
│   │       ├── data/
│   │       │   ├── antenna-patterns.json
│   │       │   └── cloudflare-colos.json
│   │       ├── downloads/
│   │       │   ├── iperf3_3.18-1_mips-3.4.ipk
│   │       │   └── libiperf3_3.18-1_mips-3.4.ipk
│   │       ├── js/
│   │       │   ├── demo-mask.js
│   │       │   ├── floorPlanEditor.js
│   │       │   ├── scrollRestoration.js
│   │       │   ├── steppedScaleBar.js
│   │       │   └── updateCheck.js
│   │       ├── lib/
│   │       │   ├── pdf.min.mjs
│   │       │   └── pdf.worker.min.mjs
│   │       └── manifest.webmanifest
│   ├── NetworkOptimizer.WiFi/
│   │   ├── Analyzers/
│   │   │   └── SiteHealthScorer.cs
│   │   ├── BssidIdentifier.cs
│   │   ├── Data/
│   │   │   ├── AntennaPatternLoader.cs
│   │   │   ├── ApModelCatalog.cs
│   │   │   ├── MaterialAttenuation.cs
│   │   │   └── MountTypeHelper.cs
│   │   ├── Helpers/
│   │   │   ├── ChannelSpanHelper.cs
│   │   │   └── SignalClassification.cs
│   │   ├── IWiFiDataProvider.cs
│   │   ├── Models/
│   │   │   ├── AccessPointSnapshot.cs
│   │   │   ├── ChannelRecommendation.cs
│   │   │   ├── ChannelScanResult.cs
│   │   │   ├── ClientConnectionEvent.cs
│   │   │   ├── PropagationModels.cs
│   │   │   ├── RegulatoryChannelData.cs
│   │   │   ├── RoamingEvent.cs
│   │   │   ├── RoamingTopology.cs
│   │   │   ├── WiFiMetrics.cs
│   │   │   ├── WirelessClientSnapshot.cs
│   │   │   └── WlanConfiguration.cs
│   │   ├── NetworkOptimizer.WiFi.csproj
│   │   ├── Providers/
│   │   │   └── UniFiLiveDataProvider.cs
│   │   ├── Rules/
│   │   │   ├── BandSteeringRule.cs
│   │   │   ├── CoChannelInterferenceRule.cs
│   │   │   ├── CoverageGapRule.cs
│   │   │   ├── DhcpIssuesRule.cs
│   │   │   ├── High2GHzConcentrationRule.cs
│   │   │   ├── HighApLoadRule.cs
│   │   │   ├── HighPowerOverlapRule.cs
│   │   │   ├── HighPowerRule.cs
│   │   │   ├── HighRadioUtilizationRule.cs
│   │   │   ├── HighTxRetryRule.cs
│   │   │   ├── IWiFiOptimizerRule.cs
│   │   │   ├── IoTSsidSeparationRule.cs
│   │   │   ├── LegacyClientAirtimeRule.cs
│   │   │   ├── LoadImbalanceRule.cs
│   │   │   ├── MinRssiEnabledRule.cs
│   │   │   ├── MinRssiRule.cs
│   │   │   ├── MinimumDataRatesRule.cs
│   │   │   ├── NonStandardChannelRule.cs
│   │   │   ├── RoamingAssistantRule.cs
│   │   │   ├── TxPowerVariationRule.cs
│   │   │   ├── WeakSignalPopulationRule.cs
│   │   │   ├── WiFiOptimizerContext.cs
│   │   │   ├── WiFiOptimizerEngine.cs
│   │   │   └── WideChannelWidthRule.cs
│   │   ├── Services/
│   │   │   ├── ChannelRecommendationService.cs
│   │   │   └── PropagationService.cs
│   │   ├── SiteHealthScore.cs
│   │   └── WiFiAnalysisHelpers.cs
│   ├── OpenSpeedTest/
│   │   ├── .gitignore
│   │   ├── ATTRIBUTION.md
│   │   ├── License.md
│   │   ├── README.md
│   │   ├── assets/
│   │   │   ├── css/
│   │   │   │   ├── app.css
│   │   │   │   ├── darkmode.css
│   │   │   │   └── ozark-overrides.css
│   │   │   ├── images/
│   │   │   │   └── icons/
│   │   │   │       ├── browserconfig.xml
│   │   │   │       └── site.webmanifest
│   │   │   └── js/
│   │   │       ├── app-2.5.4.js
│   │   │       ├── config.js
│   │   │       ├── darkmode.js
│   │   │       └── geolocation.js
│   │   ├── downloading
│   │   ├── hosted.html
│   │   ├── index.html
│   │   └── upload
│   ├── cfspeedtest/
│   │   ├── .gitignore
│   │   ├── Makefile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── speedtest/
│   │       ├── latency.go
│   │       ├── metadata.go
│   │       ├── servertiming.go
│   │       ├── sockopt_unix.go
│   │       ├── sockopt_windows.go
│   │       ├── throughput.go
│   │       ├── transport.go
│   │       └── types.go
│   ├── uwnspeedtest/
│   │   ├── Makefile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── uwn/
│   │       ├── discovery.go
│   │       ├── latency.go
│   │       ├── throughput.go
│   │       └── types.go
│   └── wansteer/
│       ├── Makefile
│       ├── config.go
│       ├── config.sample.json
│       ├── go.mod
│       ├── health.go
│       ├── main.go
│       ├── rules.go
│       ├── status.go
│       └── wansteer_test.go
└── tests/
    ├── Directory.Build.props
    ├── FluentAssertionsLicense.cs
    ├── NetworkOptimizer.Agents.Tests/
    │   ├── DeploymentResultTests.cs
    │   ├── NetworkOptimizer.Agents.Tests.csproj
    │   └── ScriptRendererTests.cs
    ├── NetworkOptimizer.Alerts.Tests/
    │   ├── AlertCooldownTrackerTests.cs
    │   ├── AlertCorrelationServiceTests.cs
    │   ├── AlertEventBusTests.cs
    │   ├── AlertRuleEvaluatorTests.cs
    │   ├── Delivery/
    │   │   ├── NtfyDeliveryChannelTests.cs
    │   │   └── WebhookDeliveryChannelTests.cs
    │   ├── NetworkOptimizer.Alerts.Tests.csproj
    │   └── ScheduleCalculationTests.cs
    ├── NetworkOptimizer.Audit.Tests/
    │   ├── Analyzers/
    │   │   ├── FirewallGroupHelperTests.cs
    │   │   ├── FirewallRuleAnalyzerTests.cs
    │   │   ├── FirewallRuleEvaluatorTests.cs
    │   │   ├── FirewallRuleOverlapDetectorTests.cs
    │   │   ├── FirewallRuleParserTests.cs
    │   │   ├── HttpAppIdsTests.cs
    │   │   ├── PortProfileResolutionTests.cs
    │   │   ├── PortSecurityAnalyzerTests.cs
    │   │   ├── ProtectCameraFallbackTests.cs
    │   │   ├── UpnpSecurityAnalyzerTests.cs
    │   │   └── VlanAnalyzerTests.cs
    │   ├── AuditScorerTests.cs
    │   ├── ConfigAuditEngineTests.cs
    │   ├── Constants/
    │   │   └── DetectionConstantsTests.cs
    │   ├── DeviceNameHintsTests.cs
    │   ├── Dns/
    │   │   ├── DnatDnsAnalyzerTests.cs
    │   │   ├── DnsAppIdsTests.cs
    │   │   ├── DnsSecurityAnalyzerTests.cs
    │   │   ├── DnsStampDecoderTests.cs
    │   │   ├── DohProviderRegistryTests.cs
    │   │   └── ThirdPartyDnsDetectorTests.cs
    │   ├── Models/
    │   │   ├── AuditRequestTests.cs
    │   │   ├── ClientInfoDisplayNameTests.cs
    │   │   ├── DeviceAllowanceSettingsTests.cs
    │   │   ├── FirewallActionTests.cs
    │   │   ├── FirewallRuleTests.cs
    │   │   └── NetworkPurposeExtensionsTests.cs
    │   ├── NetworkOptimizer.Audit.Tests.csproj
    │   ├── Rules/
    │   │   ├── AccessPortVlanRuleTests.cs
    │   │   ├── AuditRuleBaseTests.cs
    │   │   ├── CameraVlanRuleTests.cs
    │   │   ├── FirewallAnyAnyRuleTests.cs
    │   │   ├── IotVlanRuleTests.cs
    │   │   ├── MacRestrictionRuleTests.cs
    │   │   ├── PortIsolationRuleTests.cs
    │   │   ├── PortNameHelperTests.cs
    │   │   ├── UnusedPortRuleTests.cs
    │   │   ├── VlanPlacementCheckerTests.cs
    │   │   ├── VlanSubnetMismatchRuleTests.cs
    │   │   ├── WiredSubnetMismatchRuleTests.cs
    │   │   ├── WirelessCameraVlanRuleTests.cs
    │   │   └── WirelessIotVlanRuleTests.cs
    │   ├── Services/
    │   │   ├── DeviceTypeDetectionServiceTests.cs
    │   │   ├── FingerprintDetectorTests.cs
    │   │   ├── FirewallZoneLookupTests.cs
    │   │   └── MacOuiDetectorTests.cs
    │   └── xunit.runner.json
    ├── NetworkOptimizer.Core.Tests/
    │   ├── Caching/
    │   │   └── AsyncCachedValueTests.cs
    │   ├── Extensions/
    │   │   └── ServiceProviderExtensionsTests.cs
    │   ├── Helpers/
    │   │   ├── CloudflareIpRangesTests.cs
    │   │   ├── DisplayFormattersTests.cs
    │   │   └── NetworkUtilitiesTests.cs
    │   └── NetworkOptimizer.Core.Tests.csproj
    ├── NetworkOptimizer.Diagnostics.Tests/
    │   ├── Analyzers/
    │   │   ├── ApLockAnalyzerTests.cs
    │   │   ├── PerformanceAnalyzerTests.cs
    │   │   ├── PortProfile8021xAnalyzerTests.cs
    │   │   ├── PortProfileSuggestionAnalyzerTests.cs
    │   │   └── TrunkConsistencyAnalyzerTests.cs
    │   ├── DiagnosticsEngineTests.cs
    │   ├── NetworkOptimizer.Diagnostics.Tests.csproj
    │   └── xunit.runner.json
    ├── NetworkOptimizer.Monitoring.Tests/
    │   ├── AlertEngineTests.cs
    │   ├── AlertThresholdTests.cs
    │   ├── CellularModemStatsTests.cs
    │   ├── DeviceMetricsTests.cs
    │   ├── InterfaceMetricsTests.cs
    │   ├── MetricsAggregatorTests.cs
    │   ├── NetworkOptimizer.Monitoring.Tests.csproj
    │   ├── QmicliParserTests.cs
    │   └── SnmpConfigurationTests.cs
    ├── NetworkOptimizer.Reports.Tests/
    │   ├── BrandingOptionsTests.cs
    │   ├── NetworkOptimizer.Reports.Tests.csproj
    │   └── ReportDataTests.cs
    ├── NetworkOptimizer.Sqm.Tests/
    │   ├── BaselineCalculatorTests.cs
    │   ├── InputSanitizerTests.cs
    │   ├── LatencyMonitorTests.cs
    │   ├── NetworkOptimizer.Sqm.Tests.csproj
    │   ├── ScriptGeneratorTests.cs
    │   ├── SpeedtestIntegrationTests.cs
    │   ├── SqmManagerTests.cs
    │   └── WanInterfaceExtractionTests.cs
    ├── NetworkOptimizer.Storage.Tests/
    │   ├── AgentRepositoryTests.cs
    │   ├── AuditRepositoryTests.cs
    │   ├── CredentialProtectionServiceTests.cs
    │   ├── ModemRepositoryTests.cs
    │   ├── NetworkOptimizer.Storage.Tests.csproj
    │   ├── SettingsRepositoryTests.cs
    │   ├── SpeedTestRepositoryTests.cs
    │   ├── SqmRepositoryTests.cs
    │   ├── UniFiRepositoryTests.cs
    │   └── WanDataUsageServiceTests.cs
    ├── NetworkOptimizer.Threats.Tests/
    │   ├── BruteForceDetectorTests.cs
    │   ├── CrowdSecClientTests.cs
    │   ├── DDoSDetectorTests.cs
    │   ├── ExploitCampaignDetectorTests.cs
    │   ├── ExposureValidatorTests.cs
    │   ├── FlowInterestFilterTests.cs
    │   ├── KillChainClassifierTests.cs
    │   ├── NetworkOptimizer.Threats.Tests.csproj
    │   ├── ScanSweepDetectorTests.cs
    │   ├── ThreatEventNormalizerTests.cs
    │   └── ThreatPatternAnalyzerTests.cs
    ├── NetworkOptimizer.UniFi.Tests/
    │   ├── CgnatIpDetectionTests.cs
    │   ├── ClientIpEnricherTests.cs
    │   ├── DaisyChainPathTests.cs
    │   ├── DeviceTypeClassificationTests.cs
    │   ├── DiscoveredClientTests.cs
    │   ├── Fixtures/
    │   │   ├── NetworkTestData.cs
    │   │   └── TopologyBuilder.cs
    │   ├── GatewayApExclusionTests.cs
    │   ├── LagSpeedTests.cs
    │   ├── NetworkOptimizer.UniFi.Tests.csproj
    │   ├── NetworkPathAnalyzerIntegrationTests.cs
    │   ├── NetworkPathAnalyzerTests.cs
    │   ├── NetworkPathTests.cs
    │   ├── PathAnalysisResultTests.cs
    │   ├── PathTrace/
    │   │   └── BuildHopListTests.cs
    │   ├── RadioFormatHelperTests.cs
    │   ├── SnapshotIntegrationTests.cs
    │   ├── UniFiClientResponseTests.cs
    │   ├── UniFiFingerprintDatabaseTests.cs
    │   ├── UniFiFirewallZoneTests.cs
    │   ├── UniFiNetworkConfigTests.cs
    │   ├── UniFiProductDatabaseTests.cs
    │   ├── UniFiWlanConfigTests.cs
    │   └── WirelessRateSnapshotTests.cs
    ├── NetworkOptimizer.Web.Tests/
    │   ├── NetworkOptimizer.Web.Tests.csproj
    │   ├── WanSteerDeploymentServiceTests.cs
    │   └── WanSteerValidationTests.cs
    └── NetworkOptimizer.WiFi.Tests/
        ├── BssidIdentifierTests.cs
        ├── ChannelRecommendationServiceTests.cs
        ├── ChannelSpanHelperTests.cs
        ├── CoChannelInterferenceRuleTests.cs
        ├── CoverageGapRuleTests.cs
        ├── LoadImbalanceRuleTests.cs
        ├── NetworkOptimizer.WiFi.Tests.csproj
        ├── PropagationInterferenceTests.cs
        ├── RegulatoryChannelDataTests.cs
        ├── SignalClassificationTests.cs
        └── WiFiAnalysisHelpersTests.cs
Download .txt
Showing preview only (1,242K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (12936 symbols across 682 files)

FILE: scripts/extract-elevation0-from-images.py
  function is_pattern_line (line 31) | def is_pattern_line(r, g, b):
  function is_grid_pixel (line 36) | def is_grid_pixel(r, g, b):
  function find_row_spans (line 47) | def find_row_spans(arr, col_start, col_end, n_expected=None):
  function find_grid_center (line 90) | def find_grid_center(arr, y_start, y_end, col_start, col_end, rough_cx, ...
  function is_grid_tinted (line 117) | def is_grid_tinted(r, g, b):
  function trace_crosshair_extent (line 133) | def trace_crosshair_extent(arr, cx, cy, col_start, col_end):
  function find_horizontal_crosshairs (line 206) | def find_horizontal_crosshairs(arr, col_start, col_end):
  function compute_outer_ring_radius (line 263) | def compute_outer_ring_radius(blue_half_span, n_rings):
  function _trace_vertical_crosshair (line 276) | def _trace_vertical_crosshair(arr, cx, cy):
  function _find_ring_spacing_from_crosshair (line 316) | def _find_ring_spacing_from_crosshair(arr, cx, cy, current_radius):
  function _find_consistent_spacing (line 409) | def _find_consistent_spacing(dips, tol=5):
  function find_plots_in_column (line 437) | def find_plots_in_column(arr, col_start, col_end, n_expected=None, n_rin...
  function detect_outer_from_crosshair_extent (line 569) | def detect_outer_from_crosshair_extent(arr, cx, cy, col_start, col_end):
  function detect_grid_boundary (line 661) | def detect_grid_boundary(arr, cx, cy, pattern_radius, col_start, col_end):
  function _kasa_circle_fit (line 723) | def _kasa_circle_fit(points):
  function fit_outer_ring (line 742) | def fit_outer_ring(arr, rough_cx, rough_cy, pattern_radius, col_start, c...
  function extract_polar_pattern (line 831) | def extract_polar_pattern(arr, cx, cy, outer_radius, db_max, db_range,
  function despike (line 893) | def despike(gains, threshold=4.0):
  function cross_correlate (line 920) | def cross_correlate(extracted, reference):
  function validate_with_el90 (line 957) | def validate_with_el90(arr, el0_plots, ant_data, model, bands, db_max, d...
  function extract_pattern_with_radii (line 1040) | def extract_pattern_with_radii(arr, cx, cy, outer_radius, db_max, db_range,
  function save_debug_image (line 1084) | def save_debug_image(arr, img, el0_plots, el90_plots, calibrated_radius,...
  function assign_bands (line 1140) | def assign_bands(n_plots, filename=""):
  function extract_model_name (line 1164) | def extract_model_name(filename):
  function process_variant (line 1175) | def process_variant(arr, plots, bands, model_key, ant_data, db_max, db_r...
  function main (line 1256) | def main():

FILE: src/NetworkOptimizer.Agents/AgentDeployer.cs
  class AgentDeployer (line 13) | public class AgentDeployer
    method AgentDeployer (line 18) | public AgentDeployer(ILogger<AgentDeployer> logger, ScriptRenderer scr...
    method DeployAgentAsync (line 32) | public async Task<DeploymentResult> DeployAgentAsync(AgentConfiguratio...
    method TestConnectionAsync (line 120) | public async Task TestConnectionAsync(SshCredentials credentials, Canc...
    method DeployUniFiAgentAsync (line 154) | private async Task DeployUniFiAgentAsync(
    method DeployLinuxAgentAsync (line 218) | private async Task DeployLinuxAgentAsync(
    method VerifyDeploymentAsync (line 286) | private async Task<VerificationResult> VerifyDeploymentAsync(
    method CreateSshClient (line 366) | private SshClient CreateSshClient(SshCredentials credentials)
    method CreateSftpClient (line 398) | private SftpClient CreateSftpClient(SshCredentials credentials)
    method ExecuteCommand (line 430) | private string ExecuteCommand(SshClient client, string command)
    method UploadScript (line 447) | private void UploadScript(SftpClient sftp, string content, string remo...
    method FileExists (line 456) | private bool FileExists(SshClient client, string path)
    method AddStepAsync (line 465) | private async Task AddStepAsync(DeploymentResult result, string stepNa...

FILE: src/NetworkOptimizer.Agents/AgentHealthMonitor.cs
  class AgentHealthMonitor (line 11) | public class AgentHealthMonitor : IDisposable
    method AgentHealthMonitor (line 17) | public AgentHealthMonitor(
    method RecordHeartbeatAsync (line 32) | public async Task RecordHeartbeatAsync(string agentId, string deviceNa...
    method GetAgentStatusAsync (line 66) | public async Task<AgentStatus?> GetAgentStatusAsync(string agentId)
    method GetAllAgentsAsync (line 99) | public async Task<List<AgentStatus>> GetAllAgentsAsync()
    method GetOfflineAgentsAsync (line 132) | public async Task<List<AgentStatus>> GetOfflineAgentsAsync()
    method GetOnlineAgentsAsync (line 141) | public async Task<List<AgentStatus>> GetOnlineAgentsAsync()
    method RemoveAgentAsync (line 150) | public async Task RemoveAgentAsync(string agentId)
    method GetHealthStatsAsync (line 178) | public async Task<AgentHealthStats> GetHealthStatsAsync()
    method CleanupOldRecordsAsync (line 198) | public async Task CleanupOldRecordsAsync(TimeSpan retentionPeriod)
    method InitializeDatabase (line 228) | private void InitializeDatabase()
    method ReadAgentStatus (line 264) | private AgentStatus ReadAgentStatus(SqliteDataReader reader)
    method Dispose (line 295) | public void Dispose()
  class AgentStatus (line 305) | public class AgentStatus
  class AgentHealthStats (line 320) | public class AgentHealthStats

FILE: src/NetworkOptimizer.Agents/Models/AgentConfiguration.cs
  class AgentConfiguration (line 6) | public class AgentConfiguration
  type AgentType (line 69) | public enum AgentType

FILE: src/NetworkOptimizer.Agents/Models/DeploymentResult.cs
  class DeploymentResult (line 6) | public class DeploymentResult
    method CreateSuccess (line 56) | public static DeploymentResult CreateSuccess(string agentId, string de...
    method CreateFailure (line 71) | public static DeploymentResult CreateFailure(string agentId, string de...
  class DeploymentStep (line 87) | public class DeploymentStep
  class VerificationResult (line 118) | public class VerificationResult

FILE: src/NetworkOptimizer.Agents/Models/SshCredentials.cs
  class SshCredentials (line 6) | public class SshCredentials
    method IsValid (line 46) | public bool IsValid()
    method GetAuthenticationType (line 58) | public AuthenticationType GetAuthenticationType()
  type AuthenticationType (line 70) | public enum AuthenticationType

FILE: src/NetworkOptimizer.Agents/ScriptRenderer.cs
  class ScriptRenderer (line 11) | public class ScriptRenderer
    method ScriptRenderer (line 16) | public ScriptRenderer(ILogger<ScriptRenderer> logger, string? template...
    method RenderTemplateAsync (line 25) | public async Task<string> RenderTemplateAsync(string templateName, Age...
    method RenderTemplateStringAsync (line 63) | public async Task<string> RenderTemplateStringAsync(string templateCon...
    method GetTemplatesForAgent (line 88) | public List<string> GetTemplatesForAgent(AgentType agentType)
    method BuildScriptObject (line 111) | private ScriptObject BuildScriptObject(AgentConfiguration config)
    method ValidateTemplates (line 151) | public bool ValidateTemplates(AgentType agentType, out List<string> mi...
    method ListAvailableTemplates (line 171) | public List<string> ListAvailableTemplates()

FILE: src/NetworkOptimizer.Alerts/AlertCooldownTracker.cs
  class AlertCooldownTracker (line 8) | public class AlertCooldownTracker
    method IsInCooldown (line 15) | public bool IsInCooldown(string key, int cooldownSeconds)
    method RecordFired (line 29) | public void RecordFired(string key)
    method Cleanup (line 37) | public void Cleanup(TimeSpan maxAge)

FILE: src/NetworkOptimizer.Alerts/AlertCorrelationService.cs
  class AlertCorrelationService (line 12) | public class AlertCorrelationService
    method AlertCorrelationService (line 17) | public AlertCorrelationService(ILogger<AlertCorrelationService> logger)
    method DeriveIncidentStatus (line 25) | public static (AlertStatus Status, DateTime? ResolvedAt) DeriveInciden...
    method GetCorrelationKey (line 43) | public string? GetCorrelationKey(AlertEvent alertEvent)
    method CorrelateAsync (line 64) | public async Task<AlertIncident?> CorrelateAsync(

FILE: src/NetworkOptimizer.Alerts/AlertProcessingService.cs
  class AlertProcessingService (line 20) | public class AlertProcessingService : BackgroundService
    method AlertProcessingService (line 38) | public AlertProcessingService(
    method ExecuteAsync (line 76) | protected override async Task ExecuteAsync(CancellationToken stoppingT...
    method ProcessEventAsync (line 102) | private async Task ProcessEventAsync(AlertEvent alertEvent, Cancellati...
    method ProcessRuleMatchAsync (line 139) | private async Task ProcessRuleMatchAsync(
    method DeliverAsync (line 187) | private async Task DeliverAsync(
    method RefreshRuleCacheAsync (line 245) | private async Task RefreshRuleCacheAsync(IAlertRepository repository, ...
    method ResolveSourceUrl (line 267) | private string? ResolveSourceUrl(string? relativeUrl)
    method CleanupCooldowns (line 278) | private void CleanupCooldowns()

FILE: src/NetworkOptimizer.Alerts/AlertRuleEvaluator.cs
  class AlertRuleEvaluator (line 10) | public class AlertRuleEvaluator
    method AlertRuleEvaluator (line 15) | public AlertRuleEvaluator(AlertCooldownTracker cooldownTracker, ILogge...
    method Evaluate (line 24) | public List<AlertRule> Evaluate(AlertEvent alertEvent, IReadOnlyList<A...
    method RecordFired (line 70) | public void RecordFired(AlertRule rule, AlertEvent alertEvent)
    method MatchesEventType (line 79) | internal static bool MatchesEventType(string eventType, string pattern)
    method MeetsThreshold (line 98) | private static bool MeetsThreshold(AlertEvent alertEvent, AlertRule rule)
    method MatchesTargetDevice (line 117) | private static bool MatchesTargetDevice(string? deviceId, string? devi...

FILE: src/NetworkOptimizer.Alerts/DefaultAlertRules.cs
  class DefaultAlertRules (line 12) | public static class DefaultAlertRules
    method GetDefaults (line 14) | public static List<AlertRule> GetDefaults() =>

FILE: src/NetworkOptimizer.Alerts/Delivery/DiscordChannelConfig.cs
  class DiscordChannelConfig (line 3) | public class DiscordChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/DiscordDeliveryChannel.cs
  class DiscordDeliveryChannel (line 13) | public class DiscordDeliveryChannel : IAlertDeliveryChannel
    method DiscordDeliveryChannel (line 20) | public DiscordDeliveryChannel(ILogger<DiscordDeliveryChannel> logger, ...
    method SendAsync (line 26) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 68) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 107) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method PostAsync (line 139) | private async Task<bool> PostAsync(string url, string payload, Cancell...
    method GetSeverityColorInt (line 174) | private static int GetSeverityColorInt(AlertSeverity severity) => seve...

FILE: src/NetworkOptimizer.Alerts/Delivery/EmailChannelConfig.cs
  class EmailChannelConfig (line 3) | public class EmailChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/EmailDeliveryChannel.cs
  class EmailDeliveryChannel (line 13) | public class EmailDeliveryChannel : IAlertDeliveryChannel
    method EmailDeliveryChannel (line 22) | public EmailDeliveryChannel(ILogger<EmailDeliveryChannel> logger, ISec...
    method SendAsync (line 28) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 54) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 87) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method SendEmailAsync (line 124) | private async Task<bool> SendEmailAsync(EmailChannelConfig config, str...
    method SendEmailCoreAsync (line 172) | private async Task<(bool Success, string? Error)> SendEmailCoreAsync(E...
    method GetSecureSocketOptions (line 214) | private static SecureSocketOptions GetSecureSocketOptions(EmailChannel...
    method GetSeverityColor (line 221) | private static string GetSeverityColor(Core.Enums.AlertSeverity severi...
    method LoadTemplate (line 230) | private static string LoadTemplate(string name)

FILE: src/NetworkOptimizer.Alerts/Delivery/IAlertDeliveryChannel.cs
  type DigestSummary (line 10) | public record DigestSummary(int TotalCount, int CriticalCount, int Error...
  type IAlertDeliveryChannel (line 25) | public interface IAlertDeliveryChannel
    method SendAsync (line 35) | Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryEntry historyE...
    method SendDigestAsync (line 41) | Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntry> alerts, De...
    method TestAsync (line 46) | Task<(bool Success, string? Error)> TestAsync(DeliveryChannel channel,...

FILE: src/NetworkOptimizer.Alerts/Delivery/ISecretDecryptor.cs
  type ISecretDecryptor (line 7) | public interface ISecretDecryptor
    method Decrypt (line 9) | string Decrypt(string encrypted);
    method Encrypt (line 10) | string Encrypt(string plaintext);

FILE: src/NetworkOptimizer.Alerts/Delivery/NtfyChannelConfig.cs
  class NtfyChannelConfig (line 3) | public class NtfyChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/NtfyDeliveryChannel.cs
  class NtfyDeliveryChannel (line 15) | public class NtfyDeliveryChannel : IAlertDeliveryChannel
    method NtfyDeliveryChannel (line 23) | public NtfyDeliveryChannel(ILogger<NtfyDeliveryChannel> logger, HttpCl...
    method SendAsync (line 30) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 50) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 88) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method PostAsync (line 115) | private async Task<bool> PostAsync(NtfyChannelConfig config, string pa...
    method FormatMessage (line 166) | private static string FormatMessage(AlertEvent alertEvent)
    method MapPriority (line 197) | internal static int MapPriority(AlertSeverity severity) => severity sw...
    method MapTag (line 208) | internal static string MapTag(AlertSeverity severity) => severity switch

FILE: src/NetworkOptimizer.Alerts/Delivery/SlackChannelConfig.cs
  class SlackChannelConfig (line 3) | public class SlackChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/SlackDeliveryChannel.cs
  class SlackDeliveryChannel (line 13) | public class SlackDeliveryChannel : IAlertDeliveryChannel
    method SlackDeliveryChannel (line 20) | public SlackDeliveryChannel(ILogger<SlackDeliveryChannel> logger, Http...
    method SendAsync (line 26) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 64) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 101) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method PostAsync (line 126) | private async Task<bool> PostAsync(string url, string payload, Cancell...
    method FormatMessage (line 161) | private static string FormatMessage(AlertEvent alertEvent)
    method GetSeverityEmoji (line 182) | private static string GetSeverityEmoji(AlertSeverity severity) => seve...
    method GetSeverityColor (line 190) | private static string GetSeverityColor(AlertSeverity severity) => seve...

FILE: src/NetworkOptimizer.Alerts/Delivery/TeamsChannelConfig.cs
  class TeamsChannelConfig (line 3) | public class TeamsChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/TeamsDeliveryChannel.cs
  class TeamsDeliveryChannel (line 13) | public class TeamsDeliveryChannel : IAlertDeliveryChannel
    method TeamsDeliveryChannel (line 20) | public TeamsDeliveryChannel(ILogger<TeamsDeliveryChannel> logger, Http...
    method SendAsync (line 26) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 82) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 118) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method BuildAdaptiveCardPayload (line 142) | private static string BuildAdaptiveCardPayload(List<object> cardBody)
    method PostAsync (line 163) | private async Task<bool> PostAsync(string url, string payload, Cancell...
    method GetAdaptiveCardColor (line 198) | private static string GetAdaptiveCardColor(AlertSeverity severity) => ...

FILE: src/NetworkOptimizer.Alerts/Delivery/TimestampFormatter.cs
  class TimestampFormatter (line 6) | internal static class TimestampFormatter
    method FormatLocal (line 11) | internal static string FormatLocal(DateTime utcTime)
    method FormatLocalShort (line 20) | internal static string FormatLocalShort(DateTime utcTime)
    method ToLocal (line 26) | private static DateTime ToLocal(DateTime utcTime)
    method GetTimezoneAbbreviation (line 37) | private static string GetTimezoneAbbreviation(DateTime localTime)

FILE: src/NetworkOptimizer.Alerts/Delivery/WebhookChannelConfig.cs
  class WebhookChannelConfig (line 3) | public class WebhookChannelConfig

FILE: src/NetworkOptimizer.Alerts/Delivery/WebhookDeliveryChannel.cs
  class WebhookDeliveryChannel (line 13) | public class WebhookDeliveryChannel : IAlertDeliveryChannel
    method WebhookDeliveryChannel (line 21) | public WebhookDeliveryChannel(ILogger<WebhookDeliveryChannel> logger, ...
    method SendAsync (line 28) | public async Task<bool> SendAsync(AlertEvent alertEvent, AlertHistoryE...
    method SendDigestAsync (line 64) | public async Task<bool> SendDigestAsync(IReadOnlyList<AlertHistoryEntr...
    method TestAsync (line 89) | public async Task<(bool Success, string? Error)> TestAsync(DeliveryCha...
    method PostWithRetryAsync (line 114) | private async Task<bool> PostWithRetryAsync(WebhookChannelConfig confi...
    method ComputeHmacSha256 (line 166) | internal static string ComputeHmacSha256(string payload, string secret)
    method BuildTemplateModel (line 174) | private static object BuildTemplateModel(AlertEvent alertEvent) => new

FILE: src/NetworkOptimizer.Alerts/DigestService.cs
  class DigestService (line 14) | public class DigestService : BackgroundService
    method DigestService (line 30) | public DigestService(
    method ExecuteAsync (line 40) | protected override async Task ExecuteAsync(CancellationToken stoppingT...
    method CheckAndSendDigestsAsync (line 64) | private async Task CheckAndSendDigestsAsync(CancellationToken cancella...
    method LoadPersistedStateAsync (line 122) | private async Task LoadPersistedStateAsync(IAlertRepository repository...
    method MarkSentAsync (line 141) | private async Task MarkSentAsync(IDigestStateStore stateStore, int cha...
    method IsDue (line 155) | private bool IsDue(Models.DeliveryChannel channel)
    method CollapseAlerts (line 204) | private static IReadOnlyList<AlertHistoryEntry> CollapseAlerts(List<Al...
    method CreateCollapsed (line 255) | private static AlertHistoryEntry CreateCollapsed(AlertHistoryEntry rep...
    method GetDigestWindowStart (line 276) | private DateTime GetDigestWindowStart(Models.DeliveryChannel channel)

FILE: src/NetworkOptimizer.Alerts/Events/AlertEvent.cs
  type AlertEvent (line 9) | public record AlertEvent

FILE: src/NetworkOptimizer.Alerts/Events/AlertEventBus.cs
  class AlertEventBus (line 9) | public class AlertEventBus : IAlertEventBus
    method PublishAsync (line 19) | public ValueTask PublishAsync(AlertEvent alertEvent, CancellationToken...
    method SlowPublishAsync (line 24) | private async ValueTask SlowPublishAsync(AlertEvent alertEvent, Cancel...
    method ConsumeAsync (line 29) | public async IAsyncEnumerable<AlertEvent> ConsumeAsync(

FILE: src/NetworkOptimizer.Alerts/Events/IAlertEventBus.cs
  type IAlertEventBus (line 6) | public interface IAlertEventBus
    method PublishAsync (line 11) | ValueTask PublishAsync(AlertEvent alertEvent, CancellationToken cancel...
    method ConsumeAsync (line 16) | IAsyncEnumerable<AlertEvent> ConsumeAsync(CancellationToken cancellati...

FILE: src/NetworkOptimizer.Alerts/Interfaces/IAlertRepository.cs
  type IAlertRepository (line 9) | public interface IAlertRepository
    method GetRulesAsync (line 12) | Task<List<AlertRule>> GetRulesAsync(CancellationToken cancellationToke...
    method GetEnabledRulesAsync (line 13) | Task<List<AlertRule>> GetEnabledRulesAsync(CancellationToken cancellat...
    method GetRuleAsync (line 14) | Task<AlertRule?> GetRuleAsync(int id, CancellationToken cancellationTo...
    method SaveRuleAsync (line 15) | Task<int> SaveRuleAsync(AlertRule rule, CancellationToken cancellation...
    method UpdateRuleAsync (line 16) | Task UpdateRuleAsync(AlertRule rule, CancellationToken cancellationTok...
    method DeleteRuleAsync (line 17) | Task DeleteRuleAsync(int id, CancellationToken cancellationToken = def...
    method GetChannelsAsync (line 20) | Task<List<DeliveryChannel>> GetChannelsAsync(CancellationToken cancell...
    method GetEnabledChannelsAsync (line 21) | Task<List<DeliveryChannel>> GetEnabledChannelsAsync(CancellationToken ...
    method GetChannelAsync (line 22) | Task<DeliveryChannel?> GetChannelAsync(int id, CancellationToken cance...
    method SaveChannelAsync (line 23) | Task<int> SaveChannelAsync(DeliveryChannel channel, CancellationToken ...
    method UpdateChannelAsync (line 24) | Task UpdateChannelAsync(DeliveryChannel channel, CancellationToken can...
    method DeleteChannelAsync (line 25) | Task DeleteChannelAsync(int id, CancellationToken cancellationToken = ...
    method SaveAlertAsync (line 28) | Task<int> SaveAlertAsync(AlertHistoryEntry alert, CancellationToken ca...
    method UpdateAlertAsync (line 29) | Task UpdateAlertAsync(AlertHistoryEntry alert, CancellationToken cance...
    method GetActiveAlertsAsync (line 30) | Task<List<AlertHistoryEntry>> GetActiveAlertsAsync(CancellationToken c...
    method GetAlertHistoryAsync (line 31) | Task<List<AlertHistoryEntry>> GetAlertHistoryAsync(int limit = 100, st...
    method GetAlertAsync (line 32) | Task<AlertHistoryEntry?> GetAlertAsync(int id, CancellationToken cance...
    method GetAlertsForDigestAsync (line 33) | Task<List<AlertHistoryEntry>> GetAlertsForDigestAsync(DateTime since, ...
    method GetUnresolvedAlertsAsync (line 34) | Task<List<AlertHistoryEntry>> GetUnresolvedAlertsAsync(CancellationTok...
    method GetAlertsByIncidentIdAsync (line 35) | Task<List<AlertHistoryEntry>> GetAlertsByIncidentIdAsync(int incidentI...
    method SaveIncidentAsync (line 38) | Task<int> SaveIncidentAsync(AlertIncident incident, CancellationToken ...
    method UpdateIncidentAsync (line 39) | Task UpdateIncidentAsync(AlertIncident incident, CancellationToken can...
    method GetActiveIncidentByKeyAsync (line 40) | Task<AlertIncident?> GetActiveIncidentByKeyAsync(string correlationKey...
    method GetIncidentsAsync (line 41) | Task<List<AlertIncident>> GetIncidentsAsync(int limit = 50, Cancellati...
    method GetIncidentAsync (line 42) | Task<AlertIncident?> GetIncidentAsync(int id, CancellationToken cancel...

FILE: src/NetworkOptimizer.Alerts/Interfaces/IDigestStateStore.cs
  type IDigestStateStore (line 6) | public interface IDigestStateStore
    method GetLastSentAsync (line 8) | Task<DateTime?> GetLastSentAsync(int channelId, CancellationToken canc...
    method SetLastSentAsync (line 9) | Task SetLastSentAsync(int channelId, DateTime sentAt, CancellationToke...

FILE: src/NetworkOptimizer.Alerts/Interfaces/IScheduleRepository.cs
  type IScheduleRepository (line 8) | public interface IScheduleRepository
    method GetAllAsync (line 10) | Task<List<ScheduledTask>> GetAllAsync(CancellationToken cancellationTo...
    method GetEnabledAsync (line 11) | Task<List<ScheduledTask>> GetEnabledAsync(CancellationToken cancellati...
    method GetByIdAsync (line 12) | Task<ScheduledTask?> GetByIdAsync(int id, CancellationToken cancellati...
    method SaveAsync (line 13) | Task<int> SaveAsync(ScheduledTask task, CancellationToken cancellation...
    method UpdateAsync (line 14) | Task UpdateAsync(ScheduledTask task, CancellationToken cancellationTok...
    method UpdateNextRunAsync (line 15) | Task UpdateNextRunAsync(int id, DateTime nextRun, CancellationToken ca...
    method UpdateRunStatusAsync (line 16) | Task UpdateRunStatusAsync(int id, DateTime lastRun, DateTime? nextRun,...
    method DeleteAsync (line 17) | Task DeleteAsync(int id, CancellationToken cancellationToken = default);

FILE: src/NetworkOptimizer.Alerts/Models/AlertHistoryEntry.cs
  class AlertHistoryEntry (line 8) | public class AlertHistoryEntry

FILE: src/NetworkOptimizer.Alerts/Models/AlertIncident.cs
  class AlertIncident (line 8) | public class AlertIncident

FILE: src/NetworkOptimizer.Alerts/Models/AlertRule.cs
  class AlertRule (line 8) | public class AlertRule

FILE: src/NetworkOptimizer.Alerts/Models/DeliveryChannel.cs
  type DeliveryChannelType (line 8) | public enum DeliveryChannelType
  class DeliveryChannel (line 21) | public class DeliveryChannel

FILE: src/NetworkOptimizer.Alerts/Models/ScheduledTask.cs
  class ScheduledTask (line 6) | public class ScheduledTask

FILE: src/NetworkOptimizer.Alerts/ScheduleService.cs
  class ScheduleService (line 16) | public class ScheduleService : BackgroundService
    method ScheduleService (line 48) | public ScheduleService(
    method ExecuteAsync (line 58) | protected override async Task ExecuteAsync(CancellationToken stoppingT...
    method EvaluateSchedulesAsync (line 99) | private async Task EvaluateSchedulesAsync(CancellationToken ct)
    method ExecuteScheduledTaskAsync (line 163) | private async Task ExecuteScheduledTaskAsync(int taskId, string taskTy...
    method RunNowAsync (line 276) | public async Task<bool> RunNowAsync(int scheduledTaskId)
    method IsTaskRunning (line 305) | public bool IsTaskRunning(int scheduledTaskId)
    method CalculateNextRun (line 319) | public static DateTime CalculateNextRun(int frequencyMinutes, int? sta...
    method FormatTaskType (line 363) | private static string FormatTaskType(string taskType) => taskType switch

FILE: src/NetworkOptimizer.Audit/Analyzers/AuditScorer.cs
  class AuditScorer (line 11) | public class AuditScorer
    method AuditScorer (line 15) | public AuditScorer(ILogger<AuditScorer> logger)
    method CalculateScore (line 23) | public int CalculateScore(AuditResult auditResult)
    method CalculateFilteredScore (line 69) | public int CalculateFilteredScore(List<AuditIssue> filteredIssues, Aud...
    method GetScoreLabel (line 109) | public static string GetScoreLabel(int score) => score switch
    method CalculateDeductionForSeverity (line 120) | private int CalculateDeductionForSeverity(List<AuditIssue> issues, Aud...
    method CalculateHardeningBonus (line 138) | private int CalculateHardeningBonus(AuditStatistics stats, int hardeni...
    method DeterminePosture (line 167) | public SecurityPosture DeterminePosture(int score, int criticalIssues)
    method GetPostureDescription (line 190) | public string GetPostureDescription(SecurityPosture posture)
    method GetRecommendations (line 206) | public List<string> GetRecommendations(AuditResult auditResult)
    method GenerateExecutiveSummary (line 272) | public string GenerateExecutiveSummary(AuditResult auditResult)

FILE: src/NetworkOptimizer.Audit/Analyzers/FirewallGroupHelper.cs
  class FirewallGroupHelper (line 10) | public static class FirewallGroupHelper
    method ResolvePortGroup (line 15) | public static string? ResolvePortGroup(
    method ResolveAddressGroup (line 43) | public static List<string>? ResolveAddressGroup(
    method IncludesPort (line 73) | public static bool IncludesPort(string? portSpec, string port)
    method AllowsProtocol (line 116) | public static bool AllowsProtocol(string? ruleProtocol, bool matchOppo...
    method ProtocolIncludes (line 134) | private static bool ProtocolIncludes(string protocol, string target)
    method RuleAllowsPortAndProtocol (line 152) | public static bool RuleAllowsPortAndProtocol(Models.FirewallRule rule,...
    method RuleBlocksPortAndProtocol (line 173) | public static bool RuleBlocksPortAndProtocol(Models.FirewallRule rule,...
    method BlocksProtocol (line 200) | public static bool BlocksProtocol(string? ruleProtocol, bool matchOppo...

FILE: src/NetworkOptimizer.Audit/Analyzers/FirewallRuleAnalyzer.cs
  class FirewallRuleAnalyzer (line 13) | public class FirewallRuleAnalyzer
    method FirewallRuleAnalyzer (line 18) | public FirewallRuleAnalyzer(ILogger<FirewallRuleAnalyzer> logger, Fire...
    method SetFirewallGroups (line 28) | public void SetFirewallGroups(IEnumerable<UniFiFirewallGroup>? groups)
    method ExtractFirewallRules (line 34) | public List<FirewallRule> ExtractFirewallRules(JsonElement deviceData)
    method ExtractFirewallPolicies (line 40) | public List<FirewallRule> ExtractFirewallPolicies(JsonElement? firewal...
    method ParseFirewallPolicy (line 46) | public FirewallRule? ParseFirewallPolicy(JsonElement policyElement)
    method DetectShadowedRules (line 55) | public List<AuditIssue> DetectShadowedRules(List<FirewallRule> rules, ...
    method DetectPermissiveRules (line 217) | public List<AuditIssue> DetectPermissiveRules(List<FirewallRule> rules...
    method DetectOrphanedRules (line 328) | public List<AuditIssue> DetectOrphanedRules(List<FirewallRule> rules, ...
    method CheckInterVlanIsolation (line 401) | public List<AuditIssue> CheckInterVlanIsolation(List<FirewallRule> rul...
    method DetectNetworkIsolationExceptions (line 640) | public List<AuditIssue> DetectNetworkIsolationExceptions(List<Firewall...
    method GetInvolvedIsolatedNetworks (line 727) | private List<NetworkInfo> GetInvolvedIsolatedNetworks(FirewallRule rul...
    method GetIsolationExceptionPurposeSuffix (line 749) | private static string GetIsolationExceptionPurposeSuffix(List<NetworkI...
    method IsRequiredManagementAccessRule (line 798) | private static bool IsRequiredManagementAccessRule(FirewallRule rule)
    method CheckForProblematicAllowRules (line 830) | private void CheckForProblematicAllowRules(
    method CheckDirectionForProblematicAllowRule (line 854) | private void CheckDirectionForProblematicAllowRule(
    method IsDnsOnlyRule (line 902) | private static bool IsDnsOnlyRule(FirewallRule rule)
    method IsDnsPortOnly (line 922) | private static bool IsDnsPortOnly(string portSpec)
    method CheckAndAddIsolationIssue (line 943) | private void CheckAndAddIsolationIssue(
    method IsCriticalIsolationMissing (line 1050) | private static bool IsCriticalIsolationMissing(NetworkPurpose purpose1...
    method CheckInternetDisabledBroadAllow (line 1077) | public List<AuditIssue> CheckInternetDisabledBroadAllow(
    method IsBroadExternalAccess (line 1181) | private static bool IsBroadExternalAccess(
    method ParsePorts (line 1254) | private static HashSet<int> ParsePorts(string portSpec)
    method GetBroadAccessDescription (line 1287) | private static string GetBroadAccessDescription(FirewallRule rule, str...
    method AnalyzeFirewallRules (line 1317) | public List<AuditIssue> AnalyzeFirewallRules(List<FirewallRule> rules,...
    method AnalyzeManagementNetworkFirewallAccess (line 1344) | public List<AuditIssue> AnalyzeManagementNetworkFirewallAccess(List<Fi...
    method IsSourceIpBased (line 1533) | private static bool IsSourceIpBased(FirewallRule rule)
    method IsSourceMacBased (line 1542) | private static bool IsSourceMacBased(FirewallRule rule)
    method BlockRuleAffectsSameSource (line 1557) | private static bool BlockRuleAffectsSameSource(FirewallRule blockRule,...
    method IpOrCidrCoveredByAny (line 1617) | private static bool IpOrCidrCoveredByAny(string ipOrCidr, List<string>...
    method Allows5GRegistrationDomains (line 1634) | private static bool Allows5GRegistrationDomains(FirewallRule rule)
    method AppliesToDestinationNetwork (line 1649) | private static bool AppliesToDestinationNetwork(FirewallRule rule, str...
    method AppliesToDestinationNetwork (line 1700) | private static bool AppliesToDestinationNetwork(FirewallRule rule, Net...
    method DestinationCidrsCoversNetworkSubnet (line 1726) | private static bool DestinationCidrsCoversNetworkSubnet(FirewallRule r...
    method HasNetworkPair (line 1736) | private static bool HasNetworkPair(FirewallRule rule, NetworkInfo sour...
    method BlocksAllTraffic (line 1746) | private static bool BlocksAllTraffic(FirewallRule rule)
    method TargetsExternalZone (line 1777) | private static bool TargetsExternalZone(FirewallRule rule, string? ext...
    method IsAllowRuleEclipsedByBlockRule (line 1794) | private bool IsAllowRuleEclipsedByBlockRule(
    method IsNonWebAllowRuleEclipsed (line 1822) | private bool IsNonWebAllowRuleEclipsed(
    method Is5GModemAllowRuleEclipsed (line 1859) | private bool Is5GModemAllowRuleEclipsed(
    method WouldBlockSameTraffic (line 1902) | private static bool WouldBlockSameTraffic(FirewallRule blockRule, Fire...
    method BlockRuleCoversAllowProtocols (line 1962) | private static bool BlockRuleCoversAllowProtocols(FirewallRule blockRu...
    method IsExternalZoneRule (line 1984) | private static bool IsExternalZoneRule(FirewallRule rule, string? exte...
    method HasEffectiveInternetAccess (line 2000) | private bool HasEffectiveInternetAccess(
    method IsInternetBlockedViaFirewall (line 2033) | internal bool IsInternetBlockedViaFirewall(
    method MatchesInternetTrafficPattern (line 2067) | private static bool MatchesInternetTrafficPattern(FirewallRule rule, N...
    method GetExceptionPatternDescription (line 2097) | private static string GetExceptionPatternDescription(FirewallRule deny...
    method GetSourceToDestinationDescription (line 2126) | private static string GetSourceToDestinationDescription(FirewallRule r...
    method GetNetworkPurposeFromRule (line 2153) | private static string? GetNetworkPurposeFromRule(FirewallRule rule, Li...
    method GetDestinationNetworkPurposeSuffix (line 2226) | private static string GetDestinationNetworkPurposeSuffix(FirewallRule ...
    method IsKnownManagementServiceException (line 2297) | private static bool IsKnownManagementServiceException(FirewallRule all...

FILE: src/NetworkOptimizer.Audit/Analyzers/FirewallRuleEvaluator.cs
  class FirewallRuleEvaluator (line 10) | public static class FirewallRuleEvaluator
    class EvaluationResult (line 15) | public class EvaluationResult
    method Evaluate (line 64) | public static EvaluationResult Evaluate(
    method IsTrafficBlocked (line 136) | public static bool IsTrafficBlocked(
    method IsTrafficAllowed (line 150) | public static bool IsTrafficAllowed(
    method GetEffectiveBlockRule (line 164) | public static FirewallRule? GetEffectiveBlockRule(
    method GetEffectiveAllowRule (line 179) | public static FirewallRule? GetEffectiveAllowRule(

FILE: src/NetworkOptimizer.Audit/Analyzers/FirewallRuleOverlapDetector.cs
  class FirewallRuleOverlapDetector (line 12) | public static class FirewallRuleOverlapDetector
    method RulesOverlap (line 18) | public static bool RulesOverlap(FirewallRule rule1, FirewallRule rule2)
    method RulesOverlap (line 30) | public static bool RulesOverlap(FirewallRule rule1, FirewallRule rule2...
    method ZonesOverlap (line 47) | public static bool ZonesOverlap(FirewallRule rule1, FirewallRule rule2)
    method ProtocolsOverlap (line 68) | public static bool ProtocolsOverlap(FirewallRule rule1, FirewallRule r...
    method ProtocolsMatch (line 114) | private static bool ProtocolsMatch(string p1, string p2)
    method SourcesOverlap (line 131) | public static bool SourcesOverlap(FirewallRule rule1, FirewallRule rule2)
    method SourcesOverlap (line 143) | public static bool SourcesOverlap(FirewallRule rule1, FirewallRule rul...
    method ListsOverlapWithOpposite (line 193) | private static bool ListsOverlapWithOpposite<T>(
    method AllItemsInExceptionList (line 225) | private static bool AllItemsInExceptionList<T>(List<T> normalList, Lis...
    method StringListsIntersect (line 251) | private static bool StringListsIntersect(List<string> list1, List<stri...
    method DestinationsOverlap (line 260) | public static bool DestinationsOverlap(FirewallRule rule1, FirewallRul...
    method DestinationsOverlap (line 272) | public static bool DestinationsOverlap(FirewallRule rule1, FirewallRul...
    method AppsOverlap (line 385) | public static bool AppsOverlap(FirewallRule rule1, FirewallRule rule2)
    method PortsOverlap (line 434) | public static bool PortsOverlap(FirewallRule rule1, FirewallRule rule2)
    method SourcePortsOverlap (line 498) | public static bool SourcePortsOverlap(FirewallRule rule1, FirewallRule...
    method PortSetsOverlapWithOpposite (line 531) | private static bool PortSetsOverlapWithOpposite(HashSet<int> ports1, b...
    method IcmpTypesOverlap (line 556) | public static bool IcmpTypesOverlap(FirewallRule rule1, FirewallRule r...
    method IpRangesOverlap (line 582) | public static bool IpRangesOverlap(List<string> ips1, List<string> ips2)
    method IpOverlapsWithNetworks (line 609) | public static bool IpOverlapsWithNetworks(List<string>? ips, List<stri...
    method IpMatchesCidr (line 653) | public static bool IpMatchesCidr(string ip, string cidr)
    method DomainsOverlap (line 677) | public static bool DomainsOverlap(List<string> domains1, List<string> ...
    method PortStringsOverlap (line 699) | public static bool PortStringsOverlap(string ports1, string ports2)
    method ParsePortString (line 710) | public static HashSet<int> ParsePortString(string portString)
    method IsNarrowerScope (line 742) | public static bool IsNarrowerScope(FirewallRule rule1, FirewallRule ru...
    method GetSourceScopeScore (line 777) | private static int GetSourceScopeScore(FirewallRule rule)
    method GetDestinationScopeScore (line 801) | private static int GetDestinationScopeScore(FirewallRule rule)
    method GetPortSpecificityPenalty (line 849) | private static int GetPortSpecificityPenalty(string? portString)
    method GetListSizeBonus (line 868) | private static int GetListSizeBonus(int count)
    method GetCidrBonus (line 878) | private static int GetCidrBonus(List<string>? ips)

FILE: src/NetworkOptimizer.Audit/Analyzers/FirewallRuleParser.cs
  class FirewallRuleParser (line 14) | public class FirewallRuleParser
    method FirewallRuleParser (line 34) | public FirewallRuleParser(ILogger<FirewallRuleParser> logger)
    method SetFirewallGroups (line 43) | public void SetFirewallGroups(IEnumerable<UniFiFirewallGroup>? groups)
    method ExtractFirewallRules (line 58) | public List<FirewallRule> ExtractFirewallRules(JsonElement deviceData)
    method ExtractFirewallPolicies (line 102) | public List<FirewallRule> ExtractFirewallPolicies(JsonElement? firewal...
    method ParseFirewallPolicy (line 127) | public FirewallRule? ParseFirewallPolicy(JsonElement policy)
    method ParseFirewallRule (line 368) | public FirewallRule? ParseFirewallRule(JsonElement rule)
    method ResolveAddressGroup (line 693) | private List<string>? ResolveAddressGroup(string groupId)
    method ResolvePortGroup (line 699) | private string? ResolvePortGroup(string groupId)
    method MapRulesetToZones (line 709) | public static (string? SourceZoneId, string? DestinationZoneId) MapRul...
    method ParseCombinedTrafficRule (line 754) | public FirewallRule? ParseCombinedTrafficRule(JsonElement rule)
    method ExtractCombinedTrafficRules (line 853) | public List<FirewallRule> ExtractCombinedTrafficRules(JsonElement root)

FILE: src/NetworkOptimizer.Audit/Analyzers/HttpAppIds.cs
  class HttpAppIds (line 7) | public static class HttpAppIds
    method IsHttpApp (line 34) | public static bool IsHttpApp(int appId) => AllHttpAppIds.Contains(appId);
    method IsWebCategory (line 51) | public static bool IsWebCategory(int categoryId) => AllWebCategoryIds....

FILE: src/NetworkOptimizer.Audit/Analyzers/PortSecurityAnalyzer.cs
  class PortSecurityAnalyzer (line 18) | public class PortSecurityAnalyzer
    method SetProtectCameras (line 36) | public void SetProtectCameras(ProtectCameraCollection? protectCameras)
    method PortSecurityAnalyzer (line 50) | public PortSecurityAnalyzer(ILogger<PortSecurityAnalyzer> logger)
    method PortSecurityAnalyzer (line 55) | public PortSecurityAnalyzer(
    method InitializeRules (line 82) | private List<IAuditRule> InitializeRules()
    method InitializeWirelessRules (line 99) | private List<IWirelessAuditRule> InitializeWirelessRules()
    method AddRule (line 112) | public void AddRule(IAuditRule rule)
    method SetAllowanceSettings (line 120) | public void SetAllowanceSettings(DeviceAllowanceSettings settings)
    method ExtractSwitches (line 136) | public List<SwitchInfo> ExtractSwitches(JsonElement deviceData, List<N...
    method ExtractSwitches (line 145) | public List<SwitchInfo> ExtractSwitches(JsonElement deviceData, List<N...
    method ExtractSwitches (line 155) | public List<SwitchInfo> ExtractSwitches(JsonElement deviceData, List<N...
    method ExtractSwitches (line 166) | public List<SwitchInfo> ExtractSwitches(JsonElement deviceData, List<N...
    method BuildClientPortLookup (line 268) | private Dictionary<(string SwitchMac, int PortIndex), UniFiClientRespo...
    method BuildClientHistoryPortLookup (line 296) | private Dictionary<(string, int), UniFiClientDetailResponse> BuildClie...
    method ParseSwitch (line 337) | private SwitchInfo? ParseSwitch(JsonElement device, List<NetworkInfo> ...
    method ParseSwitch (line 343) | private SwitchInfo? ParseSwitch(JsonElement device, List<NetworkInfo> ...
    method ParseSwitch (line 349) | private SwitchInfo? ParseSwitch(
    method DetermineDeviceRole (line 447) | private (bool IsGateway, bool IsAccessPoint) DetermineDeviceRole(JsonE...
    method ParseSwitchCapabilities (line 487) | private SwitchCapabilities ParseSwitchCapabilities(JsonElement device)
    method ParsePort (line 509) | private PortInfo? ParsePort(JsonElement port, SwitchInfo switchInfo, L...
    method ParsePort (line 515) | private PortInfo? ParsePort(
    method AnalyzePorts (line 710) | public List<AuditIssue> AnalyzePorts(List<SwitchInfo> switches, List<N...
    method AnalyzeHardening (line 754) | public List<string> AnalyzeHardening(List<SwitchInfo> switches, List<N...
    method CalculateStatistics (line 827) | public AuditStatistics CalculateStatistics(List<SwitchInfo> switches)
    method IsCameraDeviceName (line 854) | private static bool IsCameraDeviceName(string? portName) => DeviceName...
    type ApInfo (line 859) | public record ApInfo(string Name, string? Model, string? ModelName);
    method ExtractAccessPointLookup (line 864) | public Dictionary<string, string> ExtractAccessPointLookup(JsonElement...
    method ExtractAccessPointInfoLookup (line 874) | public Dictionary<string, ApInfo> ExtractAccessPointInfoLookup(JsonEle...
    method ExtractWirelessClients (line 911) | public List<WirelessClientInfo> ExtractWirelessClients(
    method ExtractWirelessClients (line 921) | public List<WirelessClientInfo> ExtractWirelessClients(
    method AnalyzeWirelessClients (line 1010) | public List<AuditIssue> AnalyzeWirelessClients(List<WirelessClientInfo...
    method AnalyzeProtectCameraPlacement (line 1037) | public List<AuditIssue> AnalyzeProtectCameraPlacement(
    method FindPortByUplinkMac (line 1142) | private static (SwitchInfo Switch, PortInfo Port)? FindPortByUplinkMac(

FILE: src/NetworkOptimizer.Audit/Analyzers/UpnpSecurityAnalyzer.cs
  class UpnpSecurityAnalyzer (line 12) | public class UpnpSecurityAnalyzer
    method UpnpSecurityAnalyzer (line 63) | public UpnpSecurityAnalyzer(ILogger<UpnpSecurityAnalyzer> logger)
    method Analyze (line 76) | public UpnpAnalysisResult Analyze(
    method AnalyzeStaticPortForwards (line 203) | private void AnalyzeStaticPortForwards(List<UniFiPortForwardRule> stat...
    method AnalyzeUpnpRules (line 312) | private void AnalyzeUpnpRules(List<UniFiPortForwardRule> upnpRules, Li...
    method ParsePorts (line 395) | private List<int> ParsePorts(string portSpec)
    method IsSourceRestricted (line 446) | private static bool IsSourceRestricted(UniFiPortForwardRule rule)
  class UpnpAnalysisResult (line 465) | public class UpnpAnalysisResult

FILE: src/NetworkOptimizer.Audit/Analyzers/VlanAnalyzer.cs
  class VlanAnalyzer (line 14) | public class VlanAnalyzer
    method VlanAnalyzer (line 45) | public VlanAnalyzer(ILogger<VlanAnalyzer> logger)
    method ExtractNetworks (line 53) | public List<NetworkInfo> ExtractNetworks(JsonElement deviceData, Firew...
    method ApplyPurposeOverrides (line 128) | public void ApplyPurposeOverrides(List<NetworkInfo> networks, Dictiona...
    method ParseNetwork (line 172) | private NetworkInfo? ParseNetwork(JsonElement network, FirewallZoneLoo...
    method ExtractDnsServers (line 248) | private static List<string> ExtractDnsServers(JsonElement network)
    method NormalizeSubnet (line 274) | private static string? NormalizeSubnet(string? subnet)
    method ClassifyNetwork (line 316) | public NetworkPurpose ClassifyNetwork(string networkName, string? purp...
    method ContainsWord (line 476) | private static bool ContainsWord(string text, string word)
    method IsIoTNetwork (line 506) | public bool IsIoTNetwork(string? networkName)
    method IsMediaNetwork (line 517) | public bool IsMediaNetwork(string? networkName)
    method IsHomeNetwork (line 529) | public bool IsHomeNetwork(string? networkName)
    method IsGamingNetwork (line 540) | public bool IsGamingNetwork(string? networkName)
    method IsSecurityNetwork (line 552) | public bool IsSecurityNetwork(string? networkName)
    method IsManagementNetwork (line 564) | public bool IsManagementNetwork(string? networkName)
    method FindIoTNetwork (line 575) | public NetworkInfo? FindIoTNetwork(List<NetworkInfo> networks)
    method FindSecurityNetwork (line 583) | public NetworkInfo? FindSecurityNetwork(List<NetworkInfo> networks)
    method FindPrinterNetwork (line 591) | public NetworkInfo? FindPrinterNetwork(List<NetworkInfo> networks)
    method GetNetworkDisplay (line 599) | public string GetNetworkDisplay(NetworkInfo network)
    method AnalyzeDnsConfiguration (line 608) | public List<AuditIssue> AnalyzeDnsConfiguration(List<NetworkInfo> netw...
    method AnalyzeGatewayConfiguration (line 655) | public List<AuditIssue> AnalyzeGatewayConfiguration(List<NetworkInfo> ...
    method AnalyzeManagementVlanDhcp (line 691) | public List<AuditIssue> AnalyzeManagementVlanDhcp(
    method AnalyzeNetworkIsolation (line 762) | public List<AuditIssue> AnalyzeNetworkIsolation(
    method IsIsolatedViaFirewall (line 885) | private bool IsIsolatedViaFirewall(
    method RuleBlocksToNetwork (line 942) | private static bool RuleBlocksToNetwork(FirewallRule rule, NetworkInfo...
    method AnalyzeInternetAccess (line 985) | public List<AuditIssue> AnalyzeInternetAccess(
    method HasEffectiveInternetAccess (line 1065) | private bool HasEffectiveInternetAccess(
    method AnalyzeInfrastructureVlanPlacement (line 1103) | public List<AuditIssue> AnalyzeInfrastructureVlanPlacement(JsonElement...
    method FindNetworkByIp (line 1187) | private NetworkInfo? FindNetworkByIp(string ip, List<NetworkInfo> netw...

FILE: src/NetworkOptimizer.Audit/ConfigAuditEngine.cs
  class ConfigAuditEngine (line 23) | public class ConfigAuditEngine
    class AuditContext (line 38) | private sealed class AuditContext
    class ThreatContext (line 105) | public class ThreatContext
    method ConfigAuditEngine (line 128) | public ConfigAuditEngine(
    method RunAuditAsync (line 172) | public Task<AuditResult> RunAuditAsync(string deviceDataJson, string? ...
    method RunAuditAsync (line 182) | public Task<AuditResult> RunAuditAsync(string deviceDataJson, List<Uni...
    method RunAuditAsync (line 193) | public Task<AuditResult> RunAuditAsync(string deviceDataJson, List<Uni...
    method RunAuditAsync (line 206) | public Task<AuditResult> RunAuditAsync(
    method RunAuditAsync (line 218) | public Task<AuditResult> RunAuditAsync(
    method RunAuditAsync (line 241) | public Task<AuditResult> RunAuditAsync(
    method RunAuditAsync (line 271) | public async Task<AuditResult> RunAuditAsync(AuditRequest request)
    method InitializeAuditContext (line 334) | private AuditContext InitializeAuditContext(AuditRequest request)
    method DetermineExternalZoneId (line 448) | private string? DetermineExternalZoneId(List<UniFiNetworkConfig>? netw...
    method ExecutePhase1_ExtractNetworks (line 488) | private void ExecutePhase1_ExtractNetworks(AuditContext ctx)
    method ExecutePhase2_ExtractSwitches (line 498) | private void ExecutePhase2_ExtractSwitches(AuditContext ctx)
    method ExecutePhase3_AnalyzePortSecurity (line 508) | private void ExecutePhase3_AnalyzePortSecurity(AuditContext ctx)
    method ExecutePhase3a_ProtectCameraFallback (line 550) | private void ExecutePhase3a_ProtectCameraFallback(AuditContext ctx)
    method ExecutePhase3b_AnalyzeWirelessClients (line 575) | private void ExecutePhase3b_AnalyzeWirelessClients(AuditContext ctx)
    method ExecutePhase3c_AnalyzeOfflineClients (line 593) | private void ExecutePhase3c_AnalyzeOfflineClients(AuditContext ctx)
    method BuildOnlineClientMacSet (line 634) | private static HashSet<string> BuildOnlineClientMacSet(List<UniFiClien...
    method ShouldSkipOfflineClient (line 645) | private static bool ShouldSkipOfflineClient(UniFiClientDetailResponse ...
    method DetectOfflineClientType (line 654) | private static DeviceDetectionResult DetectOfflineClientType(
    method AddOfflineClientInfo (line 671) | private void AddOfflineClientInfo(
    method CheckOfflineClientPlacement (line 690) | private void CheckOfflineClientPlacement(
    method CheckOfflineIoTPlacement (line 709) | private void CheckOfflineIoTPlacement(
    method CheckOfflineCameraPlacement (line 754) | private void CheckOfflineCameraPlacement(
    method CheckOfflinePrinterPlacement (line 790) | private void CheckOfflinePrinterPlacement(
    method CreateOfflineVlanIssue (line 831) | private static AuditIssue CreateOfflineVlanIssue(
    method ExecutePhase4_AnalyzeNetworkConfiguration (line 878) | private void ExecutePhase4_AnalyzeNetworkConfiguration(AuditContext ctx)
    method ExecutePhase5_AnalyzeFirewallRules (line 898) | private void ExecutePhase5_AnalyzeFirewallRules(AuditContext ctx)
    method ExecutePhase5b_AnalyzeDnsSecurityAsync (line 957) | private async Task ExecutePhase5b_AnalyzeDnsSecurityAsync(AuditContext...
    method ExecutePhase5c_AnalyzeUpnpSecurity (line 978) | private void ExecutePhase5c_AnalyzeUpnpSecurity(AuditContext ctx)
    method ExecutePhase5d_AnalyzeThreatExposure (line 1002) | private void ExecutePhase5d_AnalyzeThreatExposure(AuditContext ctx)
    type SourceRestrictionType (line 1082) | private enum SourceRestrictionType
    method ClassifySourceRestriction (line 1096) | private SourceRestrictionType ClassifySourceRestriction(
    method ExecutePhase6_AnalyzeHardeningMeasures (line 1130) | private void ExecutePhase6_AnalyzeHardeningMeasures(AuditContext ctx)
    method BuildAuditResult (line 1166) | private AuditResult BuildAuditResult(AuditContext ctx)
    method BuildDnsSecurityInfo (line 1185) | private static DnsSecurityInfo? BuildDnsSecurityInfo(DnsSecurityResult...
    method ExecutePhase7_CalculateSecurityScore (line 1283) | private void ExecutePhase7_CalculateSecurityScore(AuditResult auditRes...
    method RunAuditFromFileAsync (line 1298) | public async Task<AuditResult> RunAuditFromFileAsync(string jsonFilePa...
    method GetRecommendations (line 1314) | public List<string> GetRecommendations(AuditResult auditResult)
    method GenerateExecutiveSummary (line 1322) | public string GenerateExecutiveSummary(AuditResult auditResult)
    method GenerateTextReport (line 1330) | public string GenerateTextReport(AuditResult auditResult)
    method ExportToJson (line 1458) | public string ExportToJson(AuditResult auditResult)
    method SaveResults (line 1472) | public void SaveResults(AuditResult auditResult, string outputPath, st...
    method IsIotDeviceName (line 1488) | private static bool IsIotDeviceName(string? portName) => DeviceNameHin...

FILE: src/NetworkOptimizer.Audit/Constants/DetectionConstants.cs
  class DetectionConstants (line 6) | public static class DetectionConstants

FILE: src/NetworkOptimizer.Audit/DeviceNameHints.cs
  class DeviceNameHints (line 9) | public static class DeviceNameHints
    method IsIoTDeviceName (line 32) | public static bool IsIoTDeviceName(string? portName)
    method IsCameraDeviceName (line 44) | public static bool IsCameraDeviceName(string? portName)
    method IsAccessPointName (line 57) | public static bool IsAccessPointName(string? portName)

FILE: src/NetworkOptimizer.Audit/Dns/DnatDnsAnalyzer.cs
  class DnatCoverageResult (line 13) | public class DnatCoverageResult
  class DnatRuleInfo (line 69) | public class DnatRuleInfo
  class DnatDnsAnalyzer (line 141) | public class DnatDnsAnalyzer
    method Analyze (line 151) | public DnatCoverageResult Analyze(JsonElement? natRulesData, List<Netw...
    method ParseDnatDnsRules (line 284) | private List<DnatRuleInfo> ParseDnatDnsRules(JsonElement natRulesData,...
    method DestinationIncludesPort53 (line 358) | private static bool DestinationIncludesPort53(JsonElement destFilter, ...
    method ResolveFilterAddress (line 387) | private static string? ResolveFilterAddress(JsonElement filter, Dictio...
    method GetFirewallGroupIds (line 421) | private static List<string> GetFirewallGroupIds(JsonElement filter)
    method ParseSourceFilter (line 442) | private static DnatRuleInfo? ParseSourceFilter(
    method IncludesUdp (line 636) | private static bool IncludesUdp(string? protocol)
    method IncludesPort53 (line 655) | private static bool IncludesPort53(string? port)
    method CidrCoversSubnet (line 701) | public static bool CidrCoversSubnet(string ruleCidr, string networkSub...

FILE: src/NetworkOptimizer.Audit/Dns/DnsAppIds.cs
  class DnsAppIds (line 8) | public static class DnsAppIds
    method IsDnsApp (line 33) | public static bool IsDnsApp(int appId) => AllDnsAppIds.Contains(appId);
    method IsDns53App (line 38) | public static bool IsDns53App(int appId) => appId == Dns;
    method IsPort853App (line 43) | public static bool IsPort853App(int appId) => appId == DnsOverTls;
    method IsPort443App (line 48) | public static bool IsPort443App(int appId) => appId == DnsOverHttps;

FILE: src/NetworkOptimizer.Audit/Dns/DnsSecurityAnalyzer.cs
  class DnsSecurityAnalyzer (line 14) | public class DnsSecurityAnalyzer
    method DnsSecurityAnalyzer (line 38) | public DnsSecurityAnalyzer(ILogger<DnsSecurityAnalyzer> logger, ThirdP...
    method AnalyzeAsync (line 47) | public Task<DnsSecurityResult> AnalyzeAsync(JsonElement? settingsData,...
    method AnalyzeAsync (line 53) | public Task<DnsSecurityResult> AnalyzeAsync(JsonElement? settingsData,...
    method AnalyzeAsync (line 59) | public Task<DnsSecurityResult> AnalyzeAsync(JsonElement? settingsData,...
    method AnalyzeAsync (line 66) | public Task<DnsSecurityResult> AnalyzeAsync(JsonElement? settingsData,...
    method AnalyzeAsync (line 77) | public async Task<DnsSecurityResult> AnalyzeAsync(JsonElement? setting...
    method AnalyzeDohConfiguration (line 158) | private void AnalyzeDohConfiguration(JsonElement settings, DnsSecurity...
    method ParseDohSettings (line 187) | private void ParseDohSettings(JsonElement dohSettings, DnsSecurityResu...
    method ParseWanDnsSettings (line 259) | private void ParseWanDnsSettings(JsonElement dnsSettings, DnsSecurityR...
    method ExtractWanDnsFromDevices (line 286) | private void ExtractWanDnsFromDevices(JsonElement deviceData, DnsSecur...
    method EnrichWanDnsFromNetworkConfigs (line 390) | private void EnrichWanDnsFromNetworkConfigs(List<UniFiNetworkConfig> n...
    method AnalyzeFirewallRules (line 441) | private void AnalyzeFirewallRules(List<FirewallRule> firewallRules, Li...
    method AddCoveredNetworks (line 671) | private static void AddCoveredNetworks(
    method CalculateCoverage (line 687) | private bool CalculateCoverage(List<NetworkInfo> networks, HashSet<str...
    method GetCorrectDnsOrder (line 709) | private static string GetCorrectDnsOrder(List<string> servers, List<st...
    method GenerateAuditIssuesAsync (line 724) | private async Task GenerateAuditIssuesAsync(DnsSecurityResult result, ...
    method ValidateWanDnsConfigurationAsync (line 1308) | private async Task ValidateWanDnsConfigurationAsync(DnsSecurityResult ...
    method IdentifyExpectedDnsProviderAsync (line 1376) | private async Task<DohProviderInfo?> IdentifyExpectedDnsProviderAsync(...
    type WanValidationResults (line 1408) | private record WanValidationResults(
    method ValidateAllWanInterfacesAsync (line 1413) | private async Task<WanValidationResults> ValidateAllWanInterfacesAsync...
    method ValidateSingleWanInterfaceAsync (line 1438) | private async Task<List<string>> ValidateSingleWanInterfaceAsync(
    method CheckNextDnsOrdering (line 1484) | private void CheckNextDnsOrdering(WanInterfaceDns wanDns, List<string?...
    method AddDnsMismatchIssues (line 1500) | private void AddDnsMismatchIssues(
    method AddDnsOrderIssues (line 1537) | private void AddDnsOrderIssues(DnsSecurityResult result)
    method AddNoStaticDnsIssues (line 1566) | private void AddNoStaticDnsIssues(DnsSecurityResult result, List<strin...
    method BuildGlobalValidDnsTargets (line 1611) | private static HashSet<string> BuildGlobalValidDnsTargets(List<Network...
    method FindDeviceSubnetGateway (line 1639) | private static string? FindDeviceSubnetGateway(string? deviceIp, List<...
    method IsValidDeviceDns (line 1659) | private static bool IsValidDeviceDns(string dns, string? deviceIp, Has...
    method GetPrimaryGatewayIp (line 1673) | private static string? GetPrimaryGatewayIp(List<NetworkInfo> networks)
    method AnalyzeDeviceDnsConfiguration (line 1682) | private void AnalyzeDeviceDnsConfiguration(List<SwitchInfo> switches, ...
    method AnalyzeAllDeviceDnsConfiguration (line 1799) | private void AnalyzeAllDeviceDnsConfiguration(JsonElement deviceData, ...
    method AnalyzeThirdPartyDnsAsync (line 1914) | private async Task AnalyzeThirdPartyDnsAsync(List<NetworkInfo> network...
    method CheckDnsConsistencyAcrossNetworks (line 1996) | private void CheckDnsConsistencyAcrossNetworks(
    method CheckDnsIpConsistency (line 2197) | private void CheckDnsIpConsistency(
    method AnalyzeDnatDnsRules (line 2334) | private void AnalyzeDnatDnsRules(JsonElement natRulesData, List<Networ...
    method ValidateDnatRedirectTargets (line 2381) | private void ValidateDnatRedirectTargets(
    method ValidateDnatDestinationFilters (line 2521) | private void ValidateDnatDestinationFilters(
    method GetSummary (line 2549) | public DnsSecuritySummary GetSummary(DnsSecurityResult result)
    method ParseIpOrRange (line 2591) | public static List<string> ParseIpOrRange(string? ipOrRange)
    method IsValidRedirectTarget (line 2598) | public static bool IsValidRedirectTarget(string? redirectIp, HashSet<s...
  class DnsSecurityResult (line 2615) | public class DnsSecurityResult
  class DeviceDnsInfo (line 2731) | public class DeviceDnsInfo
  class WanInterfaceDns (line 2745) | public class WanInterfaceDns
  class DnsServerConfig (line 2765) | public class DnsServerConfig
  class DnsSecuritySummary (line 2777) | public class DnsSecuritySummary

FILE: src/NetworkOptimizer.Audit/Dns/DnsStampDecoder.cs
  class DnsStampDecoder (line 9) | public static class DnsStampDecoder
    type DnsProtocol (line 16) | public enum DnsProtocol : byte
    method Decode (line 30) | public static DnsStampInfo? Decode(string stamp)
    method DecodeBase64Url (line 143) | private static byte[] DecodeBase64Url(string base64Url)
    method ReadVlpString (line 160) | private static string ReadVlpString(byte[] bytes, ref int offset)
    method ReadVlpData (line 174) | private static byte[] ReadVlpData(byte[] bytes, ref int offset)
    method GetProtocolName (line 189) | private static string GetProtocolName(DnsProtocol protocol) => protoco...
  class DnsStampInfo (line 205) | public class DnsStampInfo
    method GetDisplaySummary (line 222) | public string GetDisplaySummary()

FILE: src/NetworkOptimizer.Audit/Dns/DohProviderRegistry.cs
  class DohProviderRegistry (line 9) | public static class DohProviderRegistry
    method DefaultDnsResolver (line 17) | private static async Task<string?> DefaultDnsResolver(IPAddress ipAddr...
    method ResetDnsResolver (line 26) | public static void ResetDnsResolver() => DnsResolver = DefaultDnsResol...
    method IdentifyProvider (line 129) | public static DohProviderInfo? IdentifyProvider(string hostname)
    method IdentifyProviderFromName (line 150) | public static DohProviderInfo? IdentifyProviderFromName(string serverN...
    method IdentifyProviderFromIp (line 171) | public static DohProviderInfo? IdentifyProviderFromIp(string ip)
    method IdentifyProviderFromIpWithPtrAsync (line 192) | public static async Task<(DohProviderInfo? Provider, string? ReverseDn...
    method ReverseDnsLookupAsync (line 227) | public static async Task<string?> ReverseDnsLookupAsync(string ip)
    method ExtractNextDnsProfileId (line 247) | public static string? ExtractNextDnsProfileId(string? path)
    method ExtractProfileIdFromNextDnsIpv6 (line 260) | public static string? ExtractProfileIdFromNextDnsIpv6(string? ip)
    method NextDnsIpv6MatchesProfile (line 277) | public static bool NextDnsIpv6MatchesProfile(string ip, string? expect...
  class DohProviderInfo (line 302) | public class DohProviderInfo
    method MatchesIp (line 316) | public bool MatchesIp(string ip)

FILE: src/NetworkOptimizer.Audit/Dns/ThirdPartyDnsDetector.cs
  class ThirdPartyDnsDetector (line 12) | public class ThirdPartyDnsDetector
    method ThirdPartyDnsDetector (line 17) | public ThirdPartyDnsDetector(ILogger<ThirdPartyDnsDetector> logger, Ht...
    class ThirdPartyDnsInfo (line 26) | public class ThirdPartyDnsInfo
    class ExternalDnsInfo (line 42) | public class ExternalDnsInfo
    method DetectThirdPartyDnsAsync (line 60) | public async Task<List<ThirdPartyDnsInfo>> DetectThirdPartyDnsAsync(Li...
    method DetectExternalDns (line 181) | public List<ExternalDnsInfo> DetectExternalDns(List<NetworkInfo> netwo...
    method GetPublicDnsProviderName (line 237) | private static string? GetPublicDnsProviderName(string ipAddress)
    method ProbePiholeAsync (line 257) | private async Task<(bool IsPihole, string? Version)> ProbePiholeAsync(...
    method TryProbePiholeEndpointAsync (line 296) | private async Task<(bool IsPihole, string? Version)> TryProbePiholeEnd...
    method TryProbePiholeEndpointAsync (line 351) | private async Task<(bool IsPihole, string? Version)> TryProbePiholeEnd...
    method ProbeAdGuardHomeAsync (line 415) | private async Task<(bool IsAdGuardHome, string? Version)> ProbeAdGuard...
    method TryProbeAdGuardHomeEndpointAsync (line 454) | private async Task<(bool IsAdGuardHome, string? Version)> TryProbeAdGu...
    method TryProbeAdGuardHomeEndpointAsync (line 510) | private async Task<(bool IsAdGuardHome, string? Version)> TryProbeAdGu...

FILE: src/NetworkOptimizer.Audit/IssueTypes.cs
  class IssueTypes (line 7) | public static class IssueTypes

FILE: src/NetworkOptimizer.Audit/Models/AuditIssue.cs
  class AuditIssue (line 6) | public class AuditIssue

FILE: src/NetworkOptimizer.Audit/Models/AuditRequest.cs
  class AuditRequest (line 11) | public class AuditRequest

FILE: src/NetworkOptimizer.Audit/Models/AuditResult.cs
  class AuditResult (line 6) | public class AuditResult
  class DnsSecurityInfo (line 87) | public class DnsSecurityInfo
  class ThirdPartyDnsNetwork (line 283) | public class ThirdPartyDnsNetwork
  class AuditStatistics (line 309) | public class AuditStatistics
  type SecurityPosture (line 357) | public enum SecurityPosture

FILE: src/NetworkOptimizer.Audit/Models/AuditSeverity.cs
  type AuditSeverity (line 6) | public enum AuditSeverity

FILE: src/NetworkOptimizer.Audit/Models/DeviceAllowanceSettings.cs
  class DeviceAllowanceSettings (line 8) | public class DeviceAllowanceSettings
    method IsStreamingDeviceAllowed (line 44) | public bool IsStreamingDeviceAllowed(string? vendor)
    method IsSmartSpeakerAllowed (line 62) | public bool IsSmartSpeakerAllowed(string? vendor)
    method IsSmartTVAllowed (line 80) | public bool IsSmartTVAllowed(string? vendor)
    method IsMediaPlayerAllowed (line 107) | public bool IsMediaPlayerAllowed()

FILE: src/NetworkOptimizer.Audit/Models/DeviceDetectionResult.cs
  type DetectionSource (line 8) | public enum DetectionSource
  class DeviceDetectionResult (line 44) | public class DeviceDetectionResult

FILE: src/NetworkOptimizer.Audit/Models/FirewallAction.cs
  type FirewallAction (line 6) | public enum FirewallAction
  class FirewallActionExtensions (line 20) | public static class FirewallActionExtensions
    method Parse (line 25) | public static FirewallAction Parse(string? action) =>
    method IsAllowAction (line 40) | public static bool IsAllowAction(this FirewallAction action) =>
    method IsBlockAction (line 46) | public static bool IsBlockAction(this FirewallAction action) =>

FILE: src/NetworkOptimizer.Audit/Models/FirewallRule.cs
  class FirewallRule (line 8) | public class FirewallRule
    method BlocksNewConnections (line 225) | public bool BlocksNewConnections()
    method IsAnySource (line 250) | public bool IsAnySource()
    method IsAnyDestination (line 263) | public bool IsAnyDestination()
    method AllowsNewConnections (line 276) | public bool AllowsNewConnections()
    method AppliesToSourceNetwork (line 306) | public bool AppliesToSourceNetwork(NetworkInfo network)

FILE: src/NetworkOptimizer.Audit/Models/NetworkInfo.cs
  type NetworkPurpose (line 7) | public enum NetworkPurpose
  class NetworkPurposeExtensions (line 73) | public static class NetworkPurposeExtensions
    method ToDisplayString (line 78) | public static string ToDisplayString(this NetworkPurpose purpose) => p...
  class NetworkInfo (line 99) | public class NetworkInfo

FILE: src/NetworkOptimizer.Audit/Models/OfflineClientInfo.cs
  class OfflineClientInfo (line 8) | public class OfflineClientInfo

FILE: src/NetworkOptimizer.Audit/Models/PortInfo.cs
  class PortInfo (line 8) | public class PortInfo

FILE: src/NetworkOptimizer.Audit/Models/SwitchInfo.cs
  class SwitchInfo (line 6) | public class SwitchInfo
  class SwitchCapabilities (line 86) | public class SwitchCapabilities

FILE: src/NetworkOptimizer.Audit/Models/WirelessClientInfo.cs
  class WirelessClientInfo (line 8) | public class WirelessClientInfo

FILE: src/NetworkOptimizer.Audit/Rules/AccessPortVlanRule.cs
  class AccessPortVlanRule (line 14) | public class AccessPortVlanRule : AuditRuleBase
    method SetLogger (line 50) | public static void SetLogger(ILogger logger) => _logger = logger;
    method Evaluate (line 52) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...
    method IsTrunkPort (line 199) | private static bool IsTrunkPort(string? forwardMode, string? taggedVla...
    method GetTaggedVlanInfo (line 224) | private static (int TaggedVlanCount, bool AllowsAllVlans) GetTaggedVla...
    method IsNetworkFabricDevice (line 262) | private static bool IsNetworkFabricDevice(string? deviceType)
    method HasSingleDeviceMacRestriction (line 280) | private static bool HasSingleDeviceMacRestriction(PortInfo port)

FILE: src/NetworkOptimizer.Audit/Rules/CameraVlanRule.cs
  class CameraVlanRule (line 14) | public class CameraVlanRule : AuditRuleBase
    method Evaluate (line 22) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...
    method FindProtectCameraOnPort (line 207) | private ProtectCamera? FindProtectCameraOnPort(PortInfo port)
    method EvaluateProtectCamera (line 231) | private AuditIssue? EvaluateProtectCamera(ProtectCamera camera, PortIn...

FILE: src/NetworkOptimizer.Audit/Rules/FirewallAnyAnyRule.cs
  class FirewallAnyAnyRule (line 10) | public class FirewallAnyAnyRule
    method IsAnyAnyRule (line 16) | public static bool IsAnyAnyRule(FirewallRule rule)
    method CreateIssue (line 37) | public static AuditIssue CreateIssue(FirewallRule rule)

FILE: src/NetworkOptimizer.Audit/Rules/IAuditRule.cs
  type IAuditRule (line 13) | public interface IAuditRule
    method Evaluate (line 58) | AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> networks, List<N...
  class AuditRuleBase (line 64) | public abstract class AuditRuleBase : IAuditRule
    method SetDetectionService (line 94) | public void SetDetectionService(DeviceTypeDetectionService service)
    method SetAllowanceSettings (line 102) | public void SetAllowanceSettings(DeviceAllowanceSettings settings)
    method SetProtectCameras (line 110) | public void SetProtectCameras(ProtectCameraCollection cameras)
    method Evaluate (line 115) | public abstract AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...
    method DetectDeviceType (line 121) | protected DeviceDetectionResult DetectDeviceType(PortInfo port)
    method DetectDeviceTypeForDownPort (line 163) | protected DeviceDetectionResult? DetectDeviceTypeForDownPort(PortInfo ...
    method IsAuditableDownPort (line 219) | protected bool IsAuditableDownPort(PortInfo port)
    method HasOfflineDeviceData (line 232) | protected bool HasOfflineDeviceData(PortInfo port)
    method GetNetwork (line 240) | protected NetworkInfo? GetNetwork(string? networkId, List<NetworkInfo>...
    method GetNetworkName (line 251) | protected string? GetNetworkName(string? networkId, List<NetworkInfo> ...
    method IsIoTDeviceName (line 259) | protected bool IsIoTDeviceName(string? portName) => DeviceNameHints.Is...
    method IsCameraDeviceName (line 264) | protected bool IsCameraDeviceName(string? portName) => DeviceNameHints...
    method IsAccessPointName (line 269) | protected bool IsAccessPointName(string? portName) => DeviceNameHints....
    method HasIntentionalUnrestrictedProfile (line 276) | protected static bool HasIntentionalUnrestrictedProfile(PortInfo port)
    method CreateIssue (line 294) | protected AuditIssue CreateIssue(
    method GetBestDeviceName (line 324) | private string GetBestDeviceName(PortInfo port)
    method GetFirstNonEmpty (line 361) | private static string? GetFirstNonEmpty(params string?[] values)
    method IsCustomPortName (line 375) | private static bool IsCustomPortName(string portName) => PortNameHelpe...

FILE: src/NetworkOptimizer.Audit/Rules/IWirelessAuditRule.cs
  type IWirelessAuditRule (line 10) | public interface IWirelessAuditRule
    method Evaluate (line 45) | AuditIssue? Evaluate(WirelessClientInfo client, List<NetworkInfo> netw...
  class WirelessAuditRuleBase (line 51) | public abstract class WirelessAuditRuleBase : IWirelessAuditRule
    method SetAllowanceSettings (line 68) | public void SetAllowanceSettings(DeviceAllowanceSettings settings)
    method Evaluate (line 73) | public abstract AuditIssue? Evaluate(WirelessClientInfo client, List<N...
    method GetNetwork (line 78) | protected NetworkInfo? GetNetwork(string? networkId, List<NetworkInfo>...
    method CreateIssue (line 89) | protected AuditIssue CreateIssue(

FILE: src/NetworkOptimizer.Audit/Rules/IotVlanRule.cs
  class IotVlanRule (line 12) | public class IotVlanRule : AuditRuleBase
    method Evaluate (line 20) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...

FILE: src/NetworkOptimizer.Audit/Rules/MacRestrictionRule.cs
  class MacRestrictionRule (line 10) | public class MacRestrictionRule : AuditRuleBase
    method Evaluate (line 18) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...
    method IsNetworkFabricDevice (line 117) | private static bool IsNetworkFabricDevice(string? deviceType)

FILE: src/NetworkOptimizer.Audit/Rules/PortIsolationRule.cs
  class PortIsolationRule (line 9) | public class PortIsolationRule : AuditRuleBase
    method Evaluate (line 17) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...

FILE: src/NetworkOptimizer.Audit/Rules/PortNameHelper.cs
  class PortNameHelper (line 9) | public static class PortNameHelper
    method IsDefaultPortName (line 24) | public static bool IsDefaultPortName(string? portName)
    method IsCustomPortName (line 37) | public static bool IsCustomPortName(string? portName)

FILE: src/NetworkOptimizer.Audit/Rules/UnusedPortRule.cs
  class UnusedPortRule (line 11) | public class UnusedPortRule : AuditRuleBase
    method SetLogger (line 24) | public static void SetLogger(ILogger logger) => _logger = logger;
    method SetThresholds (line 31) | public static void SetThresholds(int unusedPortDays, int namedPortDays)
    method Evaluate (line 44) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...

FILE: src/NetworkOptimizer.Audit/Rules/VlanPlacementChecker.cs
  class VlanPlacementChecker (line 13) | public static class VlanPlacementChecker
    type PlacementResult (line 18) | public record PlacementResult(
    method CheckIoTPlacement (line 35) | public static PlacementResult CheckIoTPlacement(
    method CheckIoTPlacement (line 53) | public static PlacementResult CheckIoTPlacement(
    method CheckPrinterPlacement (line 133) | public static PlacementResult CheckPrinterPlacement(
    method CheckCameraPlacement (line 221) | public static PlacementResult CheckCameraPlacement(
    method BuildMetadata (line 281) | public static Dictionary<string, object> BuildMetadata(
    method HasConfigurableSetting (line 338) | public static bool HasConfigurableSetting(ClientDeviceCategory categor...
    method GetIoTMessaging (line 352) | public static (string Message, string RecommendedAction) GetIoTMessaging(
    method GetMoveRecommendation (line 386) | public static string GetMoveRecommendation(string networkLabel, bool i...
    method GetMoveRecommendation (line 400) | public static string GetMoveRecommendation(PlacementResult placement, ...

FILE: src/NetworkOptimizer.Audit/Rules/VlanSubnetMismatchRule.cs
  class VlanSubnetMismatchRule (line 15) | public class VlanSubnetMismatchRule : WirelessAuditRuleBase
    method Evaluate (line 23) | public override AuditIssue? Evaluate(WirelessClientInfo client, List<N...
    method IsValidSubnetFormat (line 126) | private static bool IsValidSubnetFormat(string subnet)

FILE: src/NetworkOptimizer.Audit/Rules/WiredSubnetMismatchRule.cs
  class WiredSubnetMismatchRule (line 15) | public class WiredSubnetMismatchRule : AuditRuleBase
    method Evaluate (line 23) | public override AuditIssue? Evaluate(PortInfo port, List<NetworkInfo> ...
    method GetDeviceName (line 117) | private string GetDeviceName(PortInfo port, UniFi.Models.UniFiClientRe...
    method IsValidSubnetFormat (line 150) | private static bool IsValidSubnetFormat(string subnet)

FILE: src/NetworkOptimizer.Audit/Rules/WirelessCameraVlanRule.cs
  class WirelessCameraVlanRule (line 12) | public class WirelessCameraVlanRule : WirelessAuditRuleBase
    method Evaluate (line 20) | public override AuditIssue? Evaluate(WirelessClientInfo client, List<N...

FILE: src/NetworkOptimizer.Audit/Rules/WirelessIotVlanRule.cs
  class WirelessIotVlanRule (line 11) | public class WirelessIotVlanRule : WirelessAuditRuleBase
    method Evaluate (line 19) | public override AuditIssue? Evaluate(WirelessClientInfo client, List<N...

FILE: src/NetworkOptimizer.Audit/Scoring/ScoreConstants.cs
  class ScoreConstants (line 7) | public static class ScoreConstants

FILE: src/NetworkOptimizer.Audit/Services/Detectors/FingerprintDetector.cs
  class FingerprintDetector (line 30) | public class FingerprintDetector
    type VendorOverride (line 414) | private record VendorOverride(
    method FingerprintDetector (line 419) | public FingerprintDetector(UniFiFingerprintDatabase? database = null, ...
    method Detect (line 430) | public DeviceDetectionResult Detect(UniFiClientResponse? clientFingerp...
    method InferCategoryFromDeviceName (line 564) | private static ClientDeviceCategory InferCategoryFromDeviceName(string...
    method GetRecommendedNetwork (line 654) | public static NetworkPurpose GetRecommendedNetwork(ClientDeviceCategor...

FILE: src/NetworkOptimizer.Audit/Services/Detectors/MacOuiDetector.cs
  class MacOuiDetector (line 10) | public class MacOuiDetector
    method MacOuiDetector (line 260) | public MacOuiDetector()
    method MacOuiDetector (line 264) | public MacOuiDetector(IeeeOuiDatabase ieeeDatabase)
    method Detect (line 272) | public DeviceDetectionResult Detect(string macAddress)
    method NormalizeOui (line 346) | private static string NormalizeOui(string mac)

FILE: src/NetworkOptimizer.Audit/Services/Detectors/NamePatternDetector.cs
  class NamePatternDetector (line 10) | public class NamePatternDetector
    method Detect (line 191) | public DeviceDetectionResult Detect(string name)
    method DetectFromPortName (line 224) | public DeviceDetectionResult DetectFromPortName(string portName)

FILE: src/NetworkOptimizer.Audit/Services/DeviceTypeDetectionService.cs
  class DeviceTypeDetectionService (line 16) | public class DeviceTypeDetectionService
    method DeviceTypeDetectionService (line 29) | public DeviceTypeDetectionService(
    method SetClientHistory (line 47) | public void SetClientHistory(List<UniFiClientDetailResponse>? clientHi...
    method SetProtectCameras (line 67) | public void SetProtectCameras(ProtectCameraCollection? protectCameras)
    method GetProtectCameraName (line 79) | public string? GetProtectCameraName(string? mac) => _protectCameras?.G...
    method DetectDeviceType (line 88) | public DeviceDetectionResult DetectDeviceType(
    method DetectDeviceType (line 104) | public DeviceDetectionResult DetectDeviceType(
    method DetectDeviceTypeCore (line 130) | private DeviceDetectionResult DetectDeviceTypeCore(
    method DetectFromUniFiOui (line 413) | private DeviceDetectionResult DetectFromUniFiOui(string ouiName, strin...
    method CreateOuiResult (line 488) | private static DeviceDetectionResult CreateOuiResult(ClientDeviceCateg...
    method CheckObviousNameOverride (line 510) | private DeviceDetectionResult? CheckObviousNameOverride(string? name, ...
    method IsCameraName (line 1031) | private static bool IsCameraName(string nameLower)
    method ApplyCloudSecurityOverride (line 1048) | private DeviceDetectionResult ApplyCloudSecurityOverride(DeviceDetecti...
    method ApplyCameraNameSupplement (line 1092) | private DeviceDetectionResult ApplyCameraNameSupplement(DeviceDetectio...
    method ApplyWatchNameSupplement (line 1154) | private DeviceDetectionResult ApplyWatchNameSupplement(DeviceDetection...
    method IsCloudSecurityVendor (line 1215) | private static bool IsCloudSecurityVendor(string vendorLower)
    method IsThermostatName (line 1233) | private static bool IsThermostatName(string nameLower)
    method CheckVendorDefaultOverride (line 1245) | private DeviceDetectionResult? CheckVendorDefaultOverride(string? oui,...
    method DetectFromPortName (line 1370) | public DeviceDetectionResult DetectFromPortName(string portName)
    method DetectFromMac (line 1379) | public DeviceDetectionResult DetectFromMac(string macAddress)
    method ShouldBeOnIoTVlan (line 1473) | public static bool ShouldBeOnIoTVlan(ClientDeviceCategory category)
    method ShouldBeOnSecurityVlan (line 1481) | public static bool ShouldBeOnSecurityVlan(ClientDeviceCategory category)
    method IsInfrastructure (line 1489) | public static bool IsInfrastructure(ClientDeviceCategory category)
    method GetRecommendedNetwork (line 1497) | public static NetworkPurpose GetRecommendedNetwork(ClientDeviceCategor...

FILE: src/NetworkOptimizer.Audit/Services/FirewallZoneLookup.cs
  class FirewallZoneLookup (line 10) | public class FirewallZoneLookup
    method FirewallZoneLookup (line 36) | public FirewallZoneLookup(IEnumerable<UniFiFirewallZone>? zones, ILogg...
    method GetZoneById (line 69) | public UniFiFirewallZone? GetZoneById(string? zoneId)
    method GetZoneByKey (line 80) | public UniFiFirewallZone? GetZoneByKey(string zoneKey)
    method GetZoneKey (line 89) | public string? GetZoneKey(string? zoneId)
    method IsDmzZone (line 97) | public bool IsDmzZone(string? zoneId)
    method IsHotspotZone (line 106) | public bool IsHotspotZone(string? zoneId)
    method IsExternalZone (line 115) | public bool IsExternalZone(string? zoneId)
    method IsInternalZone (line 124) | public bool IsInternalZone(string? zoneId)
    method GetExternalZoneId (line 133) | public string? GetExternalZoneId()
    method GetDmzZoneId (line 141) | public string? GetDmzZoneId()
    method GetHotspotZoneId (line 149) | public string? GetHotspotZoneId()
    method ValidateWanZoneAssumption (line 161) | public bool ValidateWanZoneAssumption(string? wanNetworkName, string? ...
    method ValidateExternalZoneId (line 193) | public bool ValidateExternalZoneId(string? determinedExternalZoneId)

FILE: src/NetworkOptimizer.Audit/Services/IIeeeOuiDatabase.cs
  type IIeeeOuiDatabase (line 7) | public interface IIeeeOuiDatabase
    method InitializeAsync (line 23) | Task InitializeAsync(CancellationToken cancellationToken = default);
    method GetVendor (line 30) | string? GetVendor(string macOrOui);
    method HasVendor (line 37) | bool HasVendor(string macOrOui);

FILE: src/NetworkOptimizer.Audit/Services/IeeeOuiDatabase.cs
  class IeeeOuiDatabase (line 10) | public class IeeeOuiDatabase : IIeeeOuiDatabase
    method IeeeOuiDatabase (line 22) | public IeeeOuiDatabase(ILogger<IeeeOuiDatabase> logger, IHttpClientFac...
    method InitializeAsync (line 55) | public async Task InitializeAsync(CancellationToken cancellationToken ...
    method GetVendor (line 87) | public string? GetVendor(string macOrOui)
    method HasVendor (line 99) | public bool HasVendor(string macOrOui)
    method TryLoadFromCacheAsync (line 104) | private async Task<bool> TryLoadFromCacheAsync(string cachePath)
    method TryDownloadAndCacheAsync (line 132) | private async Task<bool> TryDownloadAndCacheAsync(string cachePath, Ca...
    method ParseOuiData (line 173) | private int ParseOuiData(string content)
    method NormalizeToOui (line 213) | private static string NormalizeToOui(string input)

FILE: src/NetworkOptimizer.Core/Caching/AsyncCachedValue.cs
  class AsyncCachedValue (line 7) | public class AsyncCachedValue<T> where T : class
    method AsyncCachedValue (line 15) | public AsyncCachedValue(Func<Task<T>> factory, TimeSpan expiry)
    method GetAsync (line 21) | public async Task<T> GetAsync(bool forceRefresh = false)
    method Invalidate (line 43) | public void Invalidate()

FILE: src/NetworkOptimizer.Core/Enums/AgentType.cs
  type AgentType (line 6) | public enum AgentType
  class AgentTypeExtensions (line 42) | public static class AgentTypeExtensions
    method GetPlatform (line 47) | public static string GetPlatform(this AgentType agentType)
    method SupportsHardwareDeployment (line 63) | public static bool SupportsHardwareDeployment(this AgentType agentType)

FILE: src/NetworkOptimizer.Core/Enums/AlertSeverity.cs
  type AlertSeverity (line 6) | public enum AlertSeverity

FILE: src/NetworkOptimizer.Core/Enums/AlertStatus.cs
  type AlertStatus (line 6) | public enum AlertStatus

FILE: src/NetworkOptimizer.Core/Enums/AuditSeverity.cs
  type AuditSeverity (line 6) | public enum AuditSeverity
  class AuditSeverityExtensions (line 37) | public static class AuditSeverityExtensions
    method GetScore (line 43) | public static int GetScore(this AuditSeverity severity)
    method GetDisplayName (line 59) | public static string GetDisplayName(this AuditSeverity severity)

FILE: src/NetworkOptimizer.Core/Enums/ClientDeviceCategory.cs
  type ClientDeviceCategory (line 7) | public enum ClientDeviceCategory
  class ClientDeviceCategoryExtensions (line 185) | public static class ClientDeviceCategoryExtensions
    method IsIoT (line 190) | public static bool IsIoT(this ClientDeviceCategory category) => catego...
    method IsSurveillance (line 213) | public static bool IsSurveillance(this ClientDeviceCategory category) ...
    method IsCloudCamera (line 225) | public static bool IsCloudCamera(this ClientDeviceCategory category) =>
    method IsCloudSurveillance (line 232) | public static bool IsCloudSurveillance(this ClientDeviceCategory categ...
    method IsLowRiskIoT (line 241) | public static bool IsLowRiskIoT(this ClientDeviceCategory category) =>...
    method IsHighRiskIoT (line 263) | public static bool IsHighRiskIoT(this ClientDeviceCategory category) =...
    method IsInfrastructure (line 277) | public static bool IsInfrastructure(this ClientDeviceCategory category...
    method IsMobile (line 290) | public static bool IsMobile(this ClientDeviceCategory category) => cat...
    method IsStationary (line 302) | public static bool IsStationary(this ClientDeviceCategory category) =>...
    method GetDisplayName (line 333) | public static string GetDisplayName(this ClientDeviceCategory category...

FILE: src/NetworkOptimizer.Core/Enums/DeviceType.cs
  type DeviceType (line 7) | public enum DeviceType
  class DeviceTypeExtensions (line 114) | public static class DeviceTypeExtensions
    method ToDisplayName (line 135) | public static string ToDisplayName(this DeviceType type) => type switch
    method IsUniFiNetworkDevice (line 161) | public static bool IsUniFiNetworkDevice(this DeviceType type) => type ...
    method IsGateway (line 176) | public static bool IsGateway(this DeviceType type) => type == DeviceTy...
    method UsesUniFiIperfStreams (line 182) | public static bool UsesUniFiIperfStreams(this DeviceType type) => type...
    method FromUniFiApiType (line 199) | public static DeviceType FromUniFiApiType(string? apiType) =>
    method FromUniFiApiType (line 226) | public static DeviceType FromUniFiApiType(string? apiType, string? model)
    method IsSmartPowerModel (line 277) | private static bool IsSmartPowerModel(string model)
    method Parse (line 290) | public static DeviceType Parse(string? value)

FILE: src/NetworkOptimizer.Core/Enums/MeasurementType.cs
  type MeasurementType (line 7) | public enum MeasurementType
  class MeasurementTypeExtensions (line 73) | public static class MeasurementTypeExtensions
    method ToMeasurementName (line 78) | public static string ToMeasurementName(this MeasurementType measuremen...
    method ParseMeasurement (line 101) | public static MeasurementType ParseMeasurement(string measurementName)
    method RequiresAgent (line 124) | public static bool RequiresAgent(this MeasurementType measurementType)

FILE: src/NetworkOptimizer.Core/Extensions/ServiceProviderExtensions.cs
  class ServiceProviderExtensions (line 8) | public static class ServiceProviderExtensions
    method WithScopedService (line 14) | public static T WithScopedService<TService, T>(this IServiceProvider p...
    method WithScopedServiceAsync (line 26) | public static async Task<T> WithScopedServiceAsync<TService, T>(this I...
    method WithScopedServiceAsync (line 38) | public static async Task WithScopedServiceAsync<TService>(this IServic...

FILE: src/NetworkOptimizer.Core/FeatureFlags.cs
  class FeatureFlags (line 6) | public static class FeatureFlags

FILE: src/NetworkOptimizer.Core/Helpers/CloudflareIpRanges.cs
  class CloudflareIpRanges (line 9) | public static class CloudflareIpRanges
    method IsCloudflareOnly (line 63) | public static bool IsCloudflareOnly(IEnumerable<string>? addresses)
    method ContainsCloudflareRange (line 104) | public static bool ContainsCloudflareRange(IEnumerable<string>? addres...
    method IsCloudflareAddress (line 116) | private static bool IsCloudflareAddress(string address)

FILE: src/NetworkOptimizer.Core/Helpers/DisplayFormatters.cs
  class DisplayFormatters (line 7) | public static class DisplayFormatters
    method StripDevicePrefix (line 31) | public static string StripDevicePrefix(string? deviceName)
    method IsDeviceTypeKeyword (line 155) | private static bool IsDeviceTypeKeyword(string value)
    method ExtractNetworkName (line 165) | public static string ExtractNetworkName(string? deviceName)
    method FormatDeviceName (line 187) | public static string FormatDeviceName(string? deviceName, bool isGateway)
    method FormatDeviceName (line 197) | public static string FormatDeviceName(string? deviceName, bool isGatew...
    method ParseDeviceOnNetworkDevice (line 209) | public static (string ClientName, string? DeviceType, string? NetworkD...
    method GetNetworkDeviceLabel (line 248) | public static string GetNetworkDeviceLabel(string? deviceType)
    method FormatNetworkWithVlan (line 270) | public static string FormatNetworkWithVlan(string? networkName, int? v...
    method FormatVlanDisplay (line 281) | public static string FormatVlanDisplay(int vlanId)
    method GetLinkStatus (line 293) | public static string GetLinkStatus(bool isUp, int speed)
    method GetPoeStatus (line 308) | public static string GetPoeStatus(double poePower, string? poeMode, bo...
    method GetPortSecurityStatus (line 319) | public static string GetPortSecurityStatus(int macCount, bool portSecu...
    method GetIsolationStatus (line 331) | public static string GetIsolationStatus(bool isolation)
    method GetWanDnsDisplay (line 344) | public static string GetWanDnsDisplay(
    method GetCorrectDnsOrder (line 410) | public static string GetCorrectDnsOrder(List<string> servers, List<str...
    method GetWanDnsStatus (line 427) | public static string GetWanDnsStatus(List<string> wanDnsServers, bool ...
    method GetDeviceDnsDisplay (line 438) | public static string GetDeviceDnsDisplay(
    method GetDeviceDnsStatus (line 469) | public static string GetDeviceDnsStatus(int totalDevicesChecked, int d...
    method GetDohStatusDisplay (line 479) | public static string GetDohStatusDisplay(
    method GetProtectionStatusDisplay (line 509) | public static string GetProtectionStatusDisplay(
    method FormatOrdinal (line 542) | public static string FormatOrdinal(int number)
    method NormalizeWanDisplay (line 567) | public static string NormalizeWanDisplay(string value)
    method IsGenericWanName (line 575) | public static bool IsGenericWanName(string? name)

FILE: src/NetworkOptimizer.Core/Helpers/JsonExtensions.cs
  class JsonExtensions (line 9) | public static class JsonExtensions
    method GetStringOrNull (line 14) | public static string? GetStringOrNull(this JsonElement element, string...
    method GetStringOrDefault (line 24) | public static string GetStringOrDefault(this JsonElement element, stri...
    method GetStringFromAny (line 35) | public static string? GetStringFromAny(this JsonElement element, param...
    method GetIntOrDefault (line 52) | public static int GetIntOrDefault(this JsonElement element, string pro...
    method GetLongOrNull (line 62) | public static long? GetLongOrNull(this JsonElement element, string pro...
    method GetDoubleOrDefault (line 73) | public static double GetDoubleOrDefault(this JsonElement element, stri...
    method GetBoolOrDefault (line 89) | public static bool GetBoolOrDefault(this JsonElement element, string p...
    method GetStringArrayOrNull (line 101) | public static List<string>? GetStringArrayOrNull(this JsonElement elem...
    method GetArrayOrEmpty (line 119) | public static IEnumerable<JsonElement> GetArrayOrEmpty(this JsonElemen...
    method GetPropertyOrNull (line 129) | public static JsonElement? GetPropertyOrNull(this JsonElement element,...
    method UnwrapDataArray (line 138) | public static IEnumerable<JsonElement> UnwrapDataArray(this JsonElemen...

FILE: src/NetworkOptimizer.Core/Helpers/NetworkFormatHelpers.cs
  class NetworkFormatHelpers (line 6) | public static class NetworkFormatHelpers
    method FormatWanInterfaceName (line 14) | public static string FormatWanInterfaceName(string interfaceName, stri...

FILE: src/NetworkOptimizer.Core/Helpers/NetworkUtilities.cs
  class NetworkUtilities (line 10) | public static class NetworkUtilities
    method DetectLocalIp (line 18) | public static string? DetectLocalIp()
    method DetectLocalIpFromInterfaces (line 36) | public static string? DetectLocalIpFromInterfaces()
    method GetAllLocalIpAddresses (line 93) | public static List<string> GetAllLocalIpAddresses()
    method IsVirtualInterface (line 156) | private static bool IsVirtualInterface(string name, string description)
    method IsIpInSubnet (line 177) | public static bool IsIpInSubnet(string ipAddress, string? cidrSubnet)
    method IsIpInSubnet (line 195) | public static bool IsIpInSubnet(IPAddress ip, string cidrSubnet)
    method IsIpInAnySubnet (line 249) | public static bool IsIpInAnySubnet(string ipAddress, IEnumerable<strin...
    method GetCidrLikePrefix (line 268) | public static string? GetCidrLikePrefix(string? value)
    method IsPrivateIpAddress (line 292) | public static bool IsPrivateIpAddress(string ipAddress)
    method IsPrivateIpAddress (line 306) | public static bool IsPrivateIpAddress(IPAddress ip)
    method IsPublicIpAddress (line 367) | public static bool IsPublicIpAddress(string ipAddress)
    method IsPublicIpAddress (line 380) | public static bool IsPublicIpAddress(IPAddress ip)
    method CidrCoversSubnet (line 396) | public static bool CidrCoversSubnet(string outerCidr, string innerSubnet)
    method AnyCidrCoversSubnet (line 448) | public static bool AnyCidrCoversSubnet(IEnumerable<string>? cidrs, str...
    method ExpandIpRange (line 477) | public static List<string> ExpandIpRange(string? ipOrRange)
    method GetPortServiceName (line 534) | public static string? GetPortServiceName(int port)
    method ParseCidr (line 604) | public static (IPAddress? Network, int PrefixLength) ParseCidr(string ...
    method NormalizeControllerUrl (line 625) | public static string NormalizeControllerUrl(string url)

FILE: src/NetworkOptimizer.Core/Helpers/ProcessUtilities.cs
  class ProcessUtilities (line 6) | public static class ProcessUtilities
    method GetIperf3Path (line 13) | public static string GetIperf3Path()

FILE: src/NetworkOptimizer.Core/Interfaces/IAgentDeployer.cs
  type IAgentDeployer (line 10) | public interface IAgentDeployer
    method DeployAgentAsync (line 18) | Task<AgentDeploymentResult> DeployAgentAsync(AgentDeployment deploymen...
    method UpdateAgentAsync (line 27) | Task<bool> UpdateAgentAsync(string agentId, string targetVersion, Canc...
    method RemoveAgentAsync (line 35) | Task<bool> RemoveAgentAsync(string agentId, CancellationToken cancella...
    method GetAllAgentStatusesAsync (line 42) | Task<List<AgentStatus>> GetAllAgentStatusesAsync(CancellationToken can...
    method GetAgentStatusAsync (line 50) | Task<AgentStatus?> GetAgentStatusAsync(string agentId, CancellationTok...
    method UpdateAgentConfigurationAsync (line 59) | Task<bool> UpdateAgentConfigurationAsync(string agentId, AgentConfigur...
    method StartAgentAsync (line 67) | Task<bool> StartAgentAsync(string agentId, CancellationToken cancellat...
    method StopAgentAsync (line 75) | Task<bool> StopAgentAsync(string agentId, CancellationToken cancellati...
    method RestartAgentAsync (line 83) | Task<bool> RestartAgentAsync(string agentId, CancellationToken cancell...
    method DiscoverPotentialHostsAsync (line 91) | Task<List<AgentHost>> DiscoverPotentialHostsAsync(string networkSegmen...
    method ValidateHostAsync (line 100) | Task<HostValidationResult> ValidateHostAsync(AgentHost host, AgentType...
    method GetAgentLogsAsync (line 109) | Task<List<AgentLog>> GetAgentLogsAsync(string agentId, int lines = 100...
  class AgentDeployment (line 115) | public class AgentDeployment
  class AgentHost (line 166) | public class AgentHost
  class SshCredentials (line 217) | public class SshCredentials
  class AgentDeploymentResult (line 243) | public class AgentDeploymentResult
  class HostValidationResult (line 279) | public class HostValidationResult

FILE: src/NetworkOptimizer.Core/Interfaces/IAuditEngine.cs
  type IAuditEngine (line 9) | public interface IAuditEngine
    method PerformAuditAsync (line 17) | Task<AuditReport> PerformAuditAsync(string siteId, CancellationToken c...
    method AuditDeviceAsync (line 25) | Task<List<AuditResult>> AuditDeviceAsync(UniFiDevice device, Cancellat...
    method AuditNetworkConfigurationAsync (line 33) | Task<List<AuditResult>> AuditNetworkConfigurationAsync(NetworkConfigur...
    method AuditSqmConfigurationAsync (line 41) | Task<List<AuditResult>> AuditSqmConfigurationAsync(SqmConfiguration sq...
    method AuditWirelessConfigurationAsync (line 49) | Task<List<AuditResult>> AuditWirelessConfigurationAsync(List<WirelessN...
    method AuditFirewallRulesAsync (line 57) | Task<List<AuditResult>> AuditFirewallRulesAsync(List<FirewallRule> fir...
    method AuditPortConfigurationsAsync (line 65) | Task<List<AuditResult>> AuditPortConfigurationsAsync(List<PortConfigur...
    method CalculateHealthScore (line 72) | int CalculateHealthScore(List<AuditResult> findings);
    method GenerateRecommendationsAsync (line 80) | Task<List<OptimizationRecommendation>> GenerateRecommendationsAsync(Li...
  class OptimizationRecommendation (line 86) | public class OptimizationRecommendation

FILE: src/NetworkOptimizer.Core/Interfaces/IMetricsStorage.cs
  type IMetricsStorage (line 10) | public interface IMetricsStorage
    method WriteMetricsAsync (line 21) | Task WriteMetricsAsync(
    method WriteDeviceHealthAsync (line 34) | Task WriteDeviceHealthAsync(UniFiDevice device, CancellationToken canc...
    method WriteSqmMetricsAsync (line 42) | Task WriteSqmMetricsAsync(string deviceId, PerformanceMetrics sqmMetri...
    method WriteAgentStatusAsync (line 49) | Task WriteAgentStatusAsync(AgentStatus agentStatus, CancellationToken ...
    method WriteAuditResultsAsync (line 56) | Task WriteAuditResultsAsync(AuditReport auditReport, CancellationToken...
    method WriteConfigurationChangeAsync (line 65) | Task WriteConfigurationChangeAsync(
    method QueryMetricsAsync (line 80) | Task<List<MetricDataPoint>> QueryMetricsAsync(
    method QueryAggregatedMetricsAsync (line 97) | Task<List<AggregatedMetricDataPoint>> QueryAggregatedMetricsAsync(
    method GetLatestMetricAsync (line 112) | Task<MetricDataPoint?> GetLatestMetricAsync(
    method HealthCheckAsync (line 122) | Task<bool> HealthCheckAsync(CancellationToken cancellationToken = defa...
    method CleanupOldMetricsAsync (line 130) | Task<long> CleanupOldMetricsAsync(int retentionDays, CancellationToken...
  class MetricDataPoint (line 136) | public class MetricDataPoint
  class AggregatedMetricDataPoint (line 157) | public class AggregatedMetricDataPoint
  class AggregatedValue (line 178) | public class AggregatedValue

FILE: src/NetworkOptimizer.Core/Interfaces/IReportGenerator.cs
  type IReportGenerator (line 9) | public interface IReportGenerator
    method GenerateOptimizationReportAsync (line 18) | Task<NetworkOptimizationReport> GenerateOptimizationReportAsync(
    method GenerateExecutiveSummaryAsync (line 29) | Task<ExecutiveSummaryReport> GenerateExecutiveSummaryAsync(
    method GenerateAuditReportAsync (line 39) | Task<FormattedAuditReport> GenerateAuditReportAsync(
    method GeneratePerformanceComparisonReportAsync (line 51) | Task<PerformanceComparisonReport> GeneratePerformanceComparisonReportA...
    method GenerateHealthDashboardAsync (line 63) | Task<HealthDashboardReport> GenerateHealthDashboardAsync(
    method GenerateAgentDeploymentReportAsync (line 72) | Task<AgentDeploymentReport> GenerateAgentDeploymentReportAsync(
    method ExportReportAsync (line 82) | Task<byte[]> ExportReportAsync(
    method ScheduleReportAsync (line 93) | Task<string> ScheduleReportAsync(
    method GetReportHistoryAsync (line 106) | Task<List<ReportMetadata>> GetReportHistoryAsync(
  class NetworkOptimizationReport (line 117) | public class NetworkOptimizationReport
  class ExecutiveSummaryReport (line 188) | public class ExecutiveSummaryReport
  class FormattedAuditReport (line 234) | public class FormattedAuditReport
  class PerformanceComparisonReport (line 265) | public class PerformanceComparisonReport
  class HealthDashboardReport (line 311) | public class HealthDashboardReport
  class AgentDeploymentReport (line 357) | public class AgentDeploymentReport
  type ReportFormat (line 403) | public enum ReportFormat
  type ReportType (line 415) | public enum ReportType
  class ReportSchedule (line 428) | public class ReportSchedule
  class ReportMetadata (line 469) | public class ReportMetadata
  class SqmAnalysis (line 510) | public class SqmAnalysis
  class PerformanceTrends (line 518) | public class PerformanceTrends
  class KeyMetrics (line 525) | public class KeyMetrics
  class RemediationAction (line 535) | public class RemediationAction
  class DeviceHealthSummary (line 544) | public class DeviceHealthSummary
  class AgentHealthSummary (line 553) | public class AgentHealthSummary
  class Alert (line 562) | public class Alert

FILE: src/NetworkOptimizer.Core/Interfaces/ISqmManager.cs
  type ISqmManager (line 9) | public interface ISqmManager
    method CaptureBaselineAsync (line 18) | Task<PerformanceBaseline> CaptureBaselineAsync(string deviceId, string...
    method GenerateOptimalConfigurationAsync (line 29) | Task<SqmConfiguration> GenerateOptimalConfigurationAsync(
    method ApplySqmConfigurationAsync (line 43) | Task<bool> ApplySqmConfigurationAsync(string siteId, SqmConfiguration ...
    method TestSqmPerformanceAsync (line 52) | Task<PerformanceMetrics> TestSqmPerformanceAsync(string deviceId, int ...
    method ComparePerformance (line 60) | PerformanceComparison ComparePerformance(PerformanceBaseline baseline,...
    method AutoTuneSqmAsync (line 68) | Task<SqmTuningResult> AutoTuneSqmAsync(string deviceId, CancellationTo...
    method ValidateConfigurationAsync (line 75) | Task<SqmValidationResult> ValidateConfigurationAsync(SqmConfiguration ...
    method GetPerformanceHistoryAsync (line 85) | Task<List<PerformanceMetrics>> GetPerformanceHistoryAsync(
    method PerformBufferbloatTestAsync (line 97) | Task<BufferbloatTestResult> PerformBufferbloatTestAsync(string deviceI...
    method RecommendPriorityRulesAsync (line 106) | Task<List<TrafficPriorityRule>> RecommendPriorityRulesAsync(
  class PerformanceComparison (line 115) | public class PerformanceComparison
  class SqmTuningResult (line 166) | public class SqmTuningResult
  class TuningAdjustment (line 202) | public class TuningAdjustment
  class SqmValidationResult (line 228) | public class SqmValidationResult
  class BufferbloatTestResult (line 254) | public class BufferbloatTestResult

FILE: src/NetworkOptimizer.Core/Interfaces/IUniFiApiClient.cs
  type IUniFiApiClient (line 9) | public interface IUniFiApiClient
    method AuthenticateAsync (line 18) | Task<bool> AuthenticateAsync(string username, string password, Cancell...
    method GetSitesAsync (line 25) | Task<List<UniFiSite>> GetSitesAsync(CancellationToken cancellationToke...
    method GetDevicesAsync (line 33) | Task<List<UniFiDevice>> GetDevicesAsync(string siteId, CancellationTok...
    method GetDeviceAsync (line 42) | Task<UniFiDevice?> GetDeviceAsync(string siteId, string deviceId, Canc...
    method GetNetworkConfigurationAsync (line 50) | Task<NetworkConfiguration> GetNetworkConfigurationAsync(string siteId,...
    method GetSqmConfigurationAsync (line 59) | Task<SqmConfiguration?> GetSqmConfigurationAsync(string siteId, string...
    method UpdateSqmConfigurationAsync (line 69) | Task<bool> UpdateSqmConfigurationAsync(string siteId, string deviceId,...
    method GetWirelessNetworksAsync (line 77) | Task<List<WirelessNetwork>> GetWirelessNetworksAsync(string siteId, Ca...
    method GetFirewallRulesAsync (line 85) | Task<List<FirewallRule>> GetFirewallRulesAsync(string siteId, Cancella...
    method GetPortConfigurationsAsync (line 94) | Task<List<PortConfiguration>> GetPortConfigurationsAsync(string siteId...
    method HealthCheckAsync (line 101) | Task<bool> HealthCheckAsync(CancellationToken cancellationToken = defa...
  class UniFiSite (line 107) | public class UniFiSite

FILE: src/NetworkOptimizer.Core/Models/AgentStatus.cs
  class AgentStatus (line 9) | public class AgentStatus
  type AgentHealthStatus (line 130) | public enum AgentHealthStatus
  class AgentMetrics (line 161) | public class AgentMetrics
  class AgentConfiguration (line 217) | public class AgentConfiguration
  class AgentLog (line 253) | public class AgentLog

FILE: src/NetworkOptimizer.Core/Models/AuditResult.cs
  class AuditResult (line 8) | public class AuditResult
  class AuditReport (line 110) | public class AuditReport
  class AuditStatistics (line 177) | public class AuditStatistics

FILE: src/NetworkOptimizer.Core/Models/NetworkConfiguration.cs
  class NetworkConfiguration (line 8) | public class NetworkConfiguration
  class VlanConfiguration (line 69) | public class VlanConfiguration
  class FirewallRule (line 135) | public class FirewallRule
  type FirewallAction (line 216) | public enum FirewallAction
  type FirewallDirection (line 226) | public enum FirewallDirection
  class PortConfiguration (line 236) | public class PortConfiguration
  type PortMode (line 312) | public enum PortMode
  class WirelessNetwork (line 321) | public class WirelessNetwork
  class GuestPortalSettings (line 382) | public class GuestPortalSettings
  class DhcpConfiguration (line 413) | public class DhcpConfiguration
  class DhcpReservation (line 439) | public class DhcpReservation
  class StaticRoute (line 465) | public class StaticRoute

FILE: src/NetworkOptimizer.Core/Models/ProtectCamera.cs
  type ProtectCamera (line 6) | public sealed record ProtectCamera
  class ProtectCameraCollection (line 47) | public sealed class ProtectCameraCollection
    method Add (line 59) | public void Add(ProtectCamera camera)
    method Add (line 67) | public void Add(string mac, string name)
    method Add (line 75) | public void Add(string mac, string name, string? connectionNetworkId, ...
    method TryGet (line 83) | public bool TryGet(string? mac, out ProtectCamera? camera)
    method ContainsMac (line 94) | public bool ContainsMac(string? mac)
    method TryGetName (line 104) | public bool TryGetName(string? mac, out string? name)
    method GetName (line 121) | public string? GetName(string? mac)
    method TryGetNetworkId (line 131) | public bool TryGetNetworkId(string? mac, out string? networkId)
    method IsNvr (line 148) | public bool IsNvr(string? mac)
    method GetAll (line 158) | public IEnumerable<ProtectCamera> GetAll() => _cameras.Values;
    method AddDriveDevice (line 166) | public void AddDriveDevice(string mac)
    method IsDriveDevice (line 171) | public bool IsDriveDevice(string? mac)

FILE: src/NetworkOptimizer.Core/Models/SqmConfiguration.cs
  class SqmConfiguration (line 6) | public class SqmConfiguration
  class WanConfiguration (line 92) | public class WanConfiguration
  class PerformanceBaseline (line 143) | public class PerformanceBaseline
  class PerformanceMetrics (line 184) | public class PerformanceMetrics
  class TrafficPriorityRule (line 230) | public class TrafficPriorityRule

FILE: src/NetworkOptimizer.Core/Models/UniFiDevice.cs
  class UniFiDevice (line 9) | public class UniFiDevice

FILE: src/NetworkOptimizer.Core/VendorSpecificAttribute.cs
  class VendorSpecificAttribute (line 8) | [AttributeUsage(
    method VendorSpecificAttribute (line 16) | public VendorSpecificAttribute(string vendor, string? notes = null)

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/ApLockAnalyzer.cs
  class ApLockAnalyzer (line 13) | public class ApLockAnalyzer
    method ApLockAnalyzer (line 18) | public ApLockAnalyzer(
    method Analyze (line 32) | public List<ApLockIssue> Analyze(
    method AnalyzeOfflineClients (line 90) | public List<ApLockIssue> AnalyzeOfflineClients(
    method BuildApLookup (line 153) | private static Dictionary<string, UniFiDeviceResponse> BuildApLookup(I...
    method GetApName (line 160) | private static string GetApName(string apMac, Dictionary<string, UniFi...
    method DetermineSeverity (line 166) | private static ApLockSeverity DetermineSeverity(ClientDeviceCategory c...
    method GenerateRecommendation (line 190) | private static string GenerateRecommendation(

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/PerformanceAnalyzer.cs
  class PerformanceAnalyzer (line 17) | public class PerformanceAnalyzer
    method PerformanceAnalyzer (line 27) | public PerformanceAnalyzer(
    method Analyze (line 38) | public List<PerformanceIssue> Analyze(
    method CheckHardwareAcceleration (line 70) | [VendorSpecific("UniFi", "Reads gateway.HardwareOffload from UniFi dev...
    method IsNetFlowEnabled (line 111) | [VendorSpecific("UniFi", "Parses UniFi settings JSON 'data' array with...
    method CheckJumboFrames (line 138) | [VendorSpecific("UniFi", "Uses GlobalSwitchSettings parsed from UniFi ...
    method CheckFlowControl (line 259) | [VendorSpecific("UniFi", "Uses GlobalSwitchSettings parsed from UniFi ...
    method CheckCellularQos (line 430) | [VendorSpecific("UniFi", "Reads UniFi WAN interfaces, modem mbb_overri...
    method CheckFlowControlPortProfiles (line 545) | [VendorSpecific("UniFi", "Reads FlowControlEnabled from port profiles ...
    method HtmlEncode (line 660) | private static string HtmlEncode(string? value) => WebUtility.HtmlEnco...
    method FindMatchingWanConfig (line 667) | internal static JsonElement? FindMatchingWanConfig(GatewayWanInterface...
    method IsCellularFailover (line 699) | internal static bool IsCellularFailover(GatewayWanInterface cellularWa...
    method GetModemDataLimit (line 715) | internal static (bool Enabled, long Bytes) GetModemDataLimit(UniFiDevi...
    method GetExcludedDevicesWithSetting (line 754) | internal static List<(UniFiDeviceResponse Device, bool EffectiveValue)...
    method CountHighSpeedAccessPorts (line 771) | internal static int CountHighSpeedAccessPorts(List<UniFiDeviceResponse...
    method GetAccessPortSpeedTiers (line 793) | internal static HashSet<int> GetAccessPortSpeedTiers(List<UniFiDeviceR...
    method IsAccessPort (line 815) | private static bool IsAccessPort(SwitchPort port)
    method CountWirelessUserDevices (line 826) | private int CountWirelessUserDevices(List<UniFiClientResponse> clients)
    method GetCellularWanConfigId (line 853) | internal static string? GetCellularWanConfigId(GatewayWanInterface cel...
    method GetTargetedAppIds (line 866) | internal static HashSet<int> GetTargetedAppIds(JsonDocument? qosRulesD...
    method BuildCategoryGapDescription (line 957) | private static string? BuildCategoryGapDescription(

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/PortProfile8021xAnalyzer.cs
  class PortProfile8021xAnalyzer (line 13) | public class PortProfile8021xAnalyzer
    method PortProfile8021xAnalyzer (line 23) | public PortProfile8021xAnalyzer(ILogger<PortProfile8021xAnalyzer>? log...
    method Analyze (line 34) | public List<PortProfile8021xIssue> Analyze(
    method IsTrunkProfile (line 109) | private static bool IsTrunkProfile(UniFiPortProfile profile)
    method GetTaggedVlanInfo (line 120) | private static (int TaggedVlanCount, bool AllowsAllVlans) GetTaggedVla...
    method IsTrunkOrApProfile (line 141) | private static bool IsTrunkOrApProfile(int taggedVlanCount, bool allow...
    method GenerateRecommendation (line 154) | private static string GenerateRecommendation(string profileName, int v...

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/PortProfileSuggestionAnalyzer.cs
  class PortProfileSuggestionAnalyzer (line 15) | public class PortProfileSuggestionAnalyzer
    method PortProfileSuggestionAnalyzer (line 31) | public PortProfileSuggestionAnalyzer(ILogger<PortProfileSuggestionAnal...
    method Analyze (line 44) | public List<PortProfileSuggestion> Analyze(
    method CollectTrunkPorts (line 624) | private List<(PortReference Reference, PortConfigSignature Signature, ...
    method BuildProfileSignatures (line 693) | private Dictionary<string, (string ProfileId, string ProfileName, Port...
    method FindMatchingProfile (line 739) | private static (string ProfileId, string ProfileName, bool ForcesPoEOf...
    method FindCompatibleProfile (line 758) | private static (string ProfileId, string ProfileName, bool ForcesPoEOf...
    method FilterCompatiblePortsForProfile (line 812) | private static List<(PortReference Reference, PortConfigSignature Sign...
    method GetNetworkName (line 859) | private static string? GetNetworkName(string? networkId, Dictionary<st...
    method GenerateProfileName (line 867) | private static string GenerateProfileName(
    method GenerateRecommendation (line 887) | private static string GenerateRecommendation(
    method GenerateCreateRecommendation (line 908) | private static string GenerateCreateRecommendation(
    method AnalyzeDisabledPorts (line 926) | private List<PortProfileSuggestion> AnalyzeDisabledPorts(
    method AnalyzeUnrestrictedAccessPorts (line 1100) | private List<PortProfileSuggestion> AnalyzeUnrestrictedAccessPorts(
    method IsUnrestrictedAccessProfile (line 1252) | private static bool IsUnrestrictedAccessProfile(UniFiPortProfile profile)
  class PortConfigSignatureEqualityComparer (line 1264) | internal class PortConfigSignatureEqualityComparer : IEqualityComparer<P...
    method Equals (line 1266) | public bool Equals(PortConfigSignature? x, PortConfigSignature? y)
    method GetHashCode (line 1273) | public int GetHashCode(PortConfigSignature obj)

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/StreamingAppIds.cs
  class StreamingAppIds (line 7) | internal static class StreamingAppIds

FILE: src/NetworkOptimizer.Diagnostics/Analyzers/TrunkConsistencyAnalyzer.cs
  class TrunkConsistencyAnalyzer (line 12) | public class TrunkConsistencyAnalyzer
    method TrunkConsistencyAnalyzer (line 16) | public TrunkConsistencyAnalyzer(ILogger<TrunkConsistencyAnalyzer>? log...
    method Analyze (line 28) | public List<TrunkConsistencyIssue> Analyze(
    method DiscoverTrunkLinks (line 82) | private List<TrunkLink> DiscoverTrunkLinks(
    method FindUplinkPort (line 158) | private static int? FindUplinkPort(UniFiDeviceResponse device)
    method CountVlanOccurrences (line 184) | private static Dictionary<string, int> CountVlanOccurrences(
    method FindVlanMismatches (line 213) | private static List<VlanMismatch> FindVlanMismatches(
    method CalculateConfidence (line 256) | private static DiagnosticConfidence CalculateConfidence(
    method GenerateRecommendation (line 283) | private static string GenerateRecommendation(

FILE: src/NetworkOptimizer.Diagnostics/DiagnosticsEngine.cs
  class DiagnosticsOptions (line 14) | public class DiagnosticsOptions
  class DiagnosticsEngine (line 50) | public class DiagnosticsEngine
    method DiagnosticsEngine (line 59) | public DiagnosticsEngine(
    method RunDiagnostics (line 88) | public DiagnosticsResult RunDiagnostics(

FILE: src/NetworkOptimizer.Diagnostics/Models/AccessPortVlanIssue.cs
  class AccessPortVlanIssue (line 9) | public class AccessPortVlanIssue

FILE: src/NetworkOptimizer.Diagnostics/Models/ApLockIssue.cs
  type ApLockSeverity (line 8) | public enum ApLockSeverity
  class ApLockIssue (line 29) | public class ApLockIssue

FILE: src/NetworkOptimizer.Diagnostics/Models/DiagnosticSeverity.cs
  type DiagnosticSeverity (line 7) | public enum DiagnosticSeverity
  type DiagnosticConfidence (line 29) | public enum DiagnosticConfidence

FILE: src/NetworkOptimizer.Diagnostics/Models/DiagnosticsResult.cs
  class DiagnosticsResult (line 6) | public class DiagnosticsResult

FILE: src/NetworkOptimizer.Diagnostics/Models/PerformanceIssue.cs
  class PerformanceIssue (line 6) | public class PerformanceIssue
  type PerformanceSeverity (line 42) | public enum PerformanceSeverity
  type PerformanceCategory (line 58) | public enum PerformanceCategory

FILE: src/NetworkOptimizer.Diagnostics/Models/PortProfile8021xIssue.cs
  class PortProfile8021xIssue (line 7) | public class PortProfile8021xIssue

FILE: src/NetworkOptimizer.Diagnostics/Models/PortProfileSuggestion.cs
  type PortProfileSuggestionSeverity (line 6) | public enum PortProfileSuggestionSeverity
  type PortProfileSuggestionType (line 23) | public enum PortProfileSuggestionType
  class PortReference (line 44) | public class PortReference
  class PortConfigSignature (line 81) | public class PortConfigSignature : IEquatable<PortConfigSignature>
    method Equals (line 132) | public bool Equals(PortConfigSignature? other)
    method Equals (line 153) | public override bool Equals(object? obj) => Equals(obj as PortConfigSi...
    method GetHashCode (line 156) | public override int GetHashCode()
  class PortProfileSuggestion (line 170) | public class PortProfileSuggestion

FILE: src/NetworkOptimizer.Diagnostics/Models/TrunkConsistencyIssue.cs
  class TrunkLink (line 6) | public class TrunkLink
  class VlanMismatch (line 52) | public class VlanMismatch
  class TrunkConsistencyIssue (line 89) | public class TrunkConsistencyIssue

FILE: src/NetworkOptimizer.Monitoring/AlertEngine.cs
  class AlertState (line 10) | internal class AlertState
  type IAlertEngine (line 24) | public interface IAlertEngine
    method AddThreshold (line 29) | void AddThreshold(AlertThreshold threshold);
    method RemoveThreshold (line 34) | void RemoveThreshold(Guid thresholdId);
    method GetThresholds (line 39) | List<AlertThreshold> GetThresholds();
    method EvaluateDeviceMetrics (line 44) | List<Alert> EvaluateDeviceMetrics(DeviceMetrics metrics);
    method EvaluateInterfaceMetrics (line 49) | List<Alert> EvaluateInterfaceMetrics(List<InterfaceMetrics> metrics);
    method GetActiveAlerts (line 54) | List<Alert> GetActiveAlerts();
    method GetAlertHistory (line 59) | List<Alert> GetAlertHistory(int maxCount = 100);
    method AcknowledgeAlert (line 64) | void AcknowledgeAlert(Guid alertId, string acknowledgedBy);
    method ResolveAlert (line 69) | void ResolveAlert(Guid alertId);
    method ClearOldAlerts (line 74) | void ClearOldAlerts(TimeSpan olderThan);
  class AlertEngine (line 80) | public class AlertEngine : IAlertEngine
    method AlertEngine (line 88) | public AlertEngine(ILogger<AlertEngine> logger)
    method AddThreshold (line 99) | public void AddThreshold(AlertThreshold threshold)
    method RemoveThreshold (line 114) | public void RemoveThreshold(Guid thresholdId)
    method GetThresholds (line 128) | public List<AlertThreshold> GetThresholds()
    method EvaluateDeviceMetrics (line 143) | public List<Alert> EvaluateDeviceMetrics(DeviceMetrics metrics)
    method EvaluateInterfaceMetrics (line 180) | public List<Alert> EvaluateInterfaceMetrics(List<InterfaceMetrics> met...
    method GetActiveAlerts (line 227) | public List<Alert> GetActiveAlerts()
    method GetAlertHistory (line 241) | public List<Alert> GetAlertHistory(int maxCount = 100)
    method AcknowledgeAlert (line 255) | public void AcknowledgeAlert(Guid alertId, string acknowledgedBy)
    method ResolveAlert (line 276) | public void ResolveAlert(Guid alertId)
    method ClearOldAlerts (line 295) | public void ClearOldAlerts(TimeSpan olderThan)
    method EvaluateDeviceThreshold (line 316) | private Alert? EvaluateDeviceThreshold(AlertThreshold threshold, Devic...
    method EvaluateInterfaceThreshold (line 340) | private Alert? EvaluateInterfaceThreshold(AlertThreshold threshold, In...
    method EvaluateThreshold (line 368) | private Alert? EvaluateThreshold(
    method CreateAlert (line 465) | private Alert CreateAlert(
    method InitializeDefaultThresholds (line 519) | private void InitializeDefaultThresholds()

FILE: src/NetworkOptimizer.Monitoring/MetricsAggregator.cs
  type MetricSource (line 9) | public enum MetricSource
  class AggregatedMetric (line 20) | public class AggregatedMetric
  class MetricsBatch (line 76) | public class MetricsBatch
  type IMetricsAggregator (line 107) | public interface IMetricsAggregator
    method AddDeviceMetrics (line 112) | void AddDeviceMetrics(DeviceMetrics deviceMetrics, MetricSource source...
    method AddInterfaceMetrics (line 117) | void AddInterfaceMetrics(List<InterfaceMetrics> interfaceMetrics, Metr...
    method AddCustomMetric (line 122) | void AddCustomMetric(string name, double value, string deviceIp, Dicti...
    method GetBatch (line 127) | MetricsBatch GetBatch();
    method ClearBatch (line 132) | void ClearBatch();
    method GetBatchCount (line 137) | int GetBatchCount();
  class MetricsAggregator (line 143) | public class MetricsAggregator : IMetricsAggregator
    method MetricsAggregator (line 150) | public MetricsAggregator(ILogger<MetricsAggregator> logger, int maxBat...
    method AddDeviceMetrics (line 159) | public void AddDeviceMetrics(DeviceMetrics deviceMetrics, MetricSource...
    method AddInterfaceMetrics (line 275) | public void AddInterfaceMetrics(List<InterfaceMetrics> interfaceMetric...
    method AddCustomMetric (line 479) | public void AddCustomMetric(string name, double value, string deviceIp...
    method GetBatch (line 509) | public MetricsBatch GetBatch()
    method ClearBatch (line 524) | public void ClearBatch()
    method GetBatchCount (line 537) | public int GetBatchCount()
    method AddMetricsToBatch (line 547) | private void AddMetricsToBatch(List<AggregatedMetric> metrics)
    method CreateMetric (line 560) | private AggregatedMetric CreateMetric(
    method CreateInterfaceMetric (line 579) | private AggregatedMetric CreateInterfaceMetric(
    method CreateBaseTags (line 599) | private Dictionary<string, string> CreateBaseTags(DeviceMetrics device)
    method CreateInterfaceTags (line 619) | private Dictionary<string, string> CreateInterfaceTags(InterfaceMetric...
    method NormalizeMetricName (line 640) | private string NormalizeMetricName(string name)

FILE: src/NetworkOptimizer.Monitoring/Models/Alert.cs
  class Alert (line 8) | public class Alert
  type ThresholdComparison (line 134) | public enum ThresholdComparison

FILE: src/NetworkOptimizer.Monitoring/Models/AlertThreshold.cs
  class AlertThreshold (line 8) | public class AlertThreshold
    method AppliesTo (line 108) | public bool AppliesTo(DeviceMetrics device)
    method AppliesTo (line 128) | public bool AppliesTo(InterfaceMetrics interfaceMetrics)
    method IsActiveNow (line 143) | public bool IsActiveNow()
    method IsExceeded (line 159) | public bool IsExceeded(double value)
  class TimeWindow (line 177) | public class TimeWindow
    method IsActive (line 197) | public bool IsActive(DateTime utcNow)

FILE: src/NetworkOptimizer.Monitoring/Models/CellularModemStats.cs
  type CellularNetworkMode (line 6) | public enum CellularNetworkMode
  class CellularModemStats (line 22) | public class CellularModemStats
    method DetermineNetworkMode (line 93) | private CellularNetworkMode DetermineNetworkMode()
    method CalculateSignalQuality (line 118) | private int CalculateSignalQuality()
  class SignalInfo (line 182) | public class SignalInfo
    method CalculateBars (line 199) | private int CalculateBars()
  class CellInfo (line 227) | public class CellInfo
  class BandInfo (line 260) | public class BandInfo
    method GetBandName (line 277) | private string GetBandName()

FILE: src/NetworkOptimizer.Monitoring/Models/DeviceMetrics.cs
  class DeviceMetrics (line 6) | public class DeviceMetrics
  type DeviceType (line 147) | public enum DeviceType

FILE: src/NetworkOptimizer.Monitoring/Models/InterfaceMetrics.cs
  class InterfaceMetrics (line 6) | public class InterfaceMetrics
    method ShouldMonitor (line 186) | public bool ShouldMonitor()

FILE: src/NetworkOptimizer.Monitoring/QmicliParser.cs
  class QmicliParser (line 9) | public static class QmicliParser
    method ParseSignalInfo (line 14) | public static (SignalInfo? lte, SignalInfo? nr5g) ParseSignalInfo(stri...
    method ParseServingSystem (line 56) | public static (string registrationState, string carrier, string mcc, s...
    method ParseCellLocationInfo (line 86) | public static (CellInfo? servingCell, List<CellInfo> neighborCells) Pa...
    method ParseRfBandInfo (line 225) | public static BandInfo? ParseRfBandInfo(string output)
    method ExtractQuotedValue (line 249) | private static string ExtractQuotedValue(string line)
    method TryParseDbValue (line 255) | private static bool TryParseDbValue(string line, string prefix, out do...
    method TryParseDbValueAlt (line 268) | private static bool TryParseDbValueAlt(string line, out double value)

FILE: src/NetworkOptimizer.Monitoring/SnmpConfiguration.cs
  class SnmpConfiguration (line 6) | public class SnmpConfiguration
    method Validate (line 112) | public void Validate()
    method Clone (line 147) | public SnmpConfiguration Clone()
  type SnmpVersion (line 176) | public enum SnmpVersion
  type AuthenticationProtocol (line 197) | public enum AuthenticationProtocol
  type PrivacyProtocol (line 233) | public enum PrivacyProtocol

FILE: src/NetworkOptimizer.Monitoring/SnmpPoller.cs
  type ISnmpPoller (line 20) | public interface ISnmpPoller
    method GetAsync (line 25) | Task<T?> GetAsync<T>(IPAddress ip, string oid);
    method WalkAsync (line 30) | Task<List<Variable>> WalkAsync(IPAddress ip, string oid);
    method GetDeviceMetricsAsync (line 35) | Task<DeviceMetrics> GetDeviceMetricsAsync(IPAddress ip, string? hostna...
    method GetInterfaceMetricsAsync (line 40) | Task<List<InterfaceMetrics>> GetInterfaceMetricsAsync(IPAddress ip, st...
    method GetSystemInfoAsync (line 45) | Task<(string hostname, string description, long uptime)> GetSystemInfo...
  class SnmpPoller (line 68) | public class SnmpPoller : ISnmpPoller
    method SnmpPoller (line 73) | public SnmpPoller(SnmpConfiguration config, ILogger<SnmpPoller> logger)
    method GetAsync (line 95) | public async Task<T?> GetAsync<T>(IPAddress ip, string oid)
    method WalkAsync (line 149) | public async Task<List<Variable>> WalkAsync(IPAddress ip, string oid)
    method GetDeviceMetricsAsync (line 188) | public async Task<DeviceMetrics> GetDeviceMetricsAsync(IPAddress ip, s...
    method GetInterfaceMetricsAsync (line 227) | public async Task<List<InterfaceMetrics>> GetInterfaceMetricsAsync(IPA...
    method GetSystemInfoAsync (line 277) | public async Task<(string hostname, string description, long uptime)> ...
    method GetSystemMetrics (line 290) | private async Task GetSystemMetrics(IPAddress ip, DeviceMetrics metrics)
    method GetResourceMetrics (line 300) | private async Task GetResourceMetrics(IPAddress ip, DeviceMetrics metr...
    method GetUniFiMetrics (line 337) | private async Task GetUniFiMetrics(IPAddress ip, DeviceMetrics metrics)
    method GetInterfaceMetricsForIndex (line 353) | private async Task<InterfaceMetrics?> GetInterfaceMetricsForIndex(IPAd...
    method ParseHostResourcesMemory (line 425) | private Task ParseHostResourcesMemory(List<Variable> storageVars, Devi...
    method DetermineDeviceType (line 433) | private DeviceType DetermineDeviceType(string model, string description)
    method GetV3 (line 455) | private IList<Variable> GetV3(IPEndPoint endpoint, List<Variable> vari...
    method GetV1V2c (line 480) | private IList<Variable> GetV1V2c(IPEndPoint endpoint, List<Variable> v...
    method WalkV3 (line 496) | private void WalkV3(IPEndPoint endpoint, ObjectIdentifier table, List<...
    method WalkV1V2c (line 533) | private void WalkV1V2c(IPEndPoint endpoint, ObjectIdentifier table, Li...
    method GetAuthenticationProvider (line 549) | private IAuthenticationProvider GetAuthenticationProvider()
    method GetPrivacyProvider (line 567) | private IPrivacyProvider GetPrivacyProvider(IAuthenticationProvider auth)
    method ConvertSnmpValue (line 588) | private T? ConvertSnmpValue<T>(ISnmpData? data)
    method DebugLog (line 657) | private void DebugLog(string message)

FILE: src/NetworkOptimizer.Monitoring/UniFiOids.cs
  class UniFiOids (line 6) | public static class UniFiOids
    method GetInterfaceOid (line 447) | public static string GetInterfaceOid(string baseOid, int interfaceIndex)
    method GetTableOid (line 455) | public static string GetTableOid(string baseOid, params int[] indices)

FILE: src/NetworkOptimizer.Reports/BrandingOptions.cs
  class BrandingOptions (line 7) | public class BrandingOptions
    method OzarkConnect (line 46) | public static BrandingOptions OzarkConnect() => new()
    method Generic (line 57) | public static BrandingOptions Generic() => new()
  class ColorScheme (line 69) | public class ColorScheme
    method OzarkConnect (line 120) | public static ColorScheme OzarkConnect() => new()
    method Generic (line 136) | public static ColorScheme Generic() => new()
    method HighContrast (line 152) | public static ColorScheme HighContrast() => new()
    method HexToRgb (line 168) | public static (float R, float G, float B) HexToRgb(string hex)
    method GetPrimaryRgb (line 184) | public (float R, float G, float B) GetPrimaryRgb() => HexToRgb(Primary);
    method GetSecondaryRgb (line 189) | public (float R, float G, float B) GetSecondaryRgb() => HexToRgb(Secon...
    method GetSuccessRgb (line 194) | public (float R, float G, float B) GetSuccessRgb() => HexToRgb(Success);
    method GetWarningRgb (line 199) | public (float R, float G, float B) GetWarningRgb() => HexToRgb(Warning);
    method GetCriticalRgb (line 204) | public (float R, float G, float B) GetCriticalRgb() => HexToRgb(Critic...

FILE: src/NetworkOptimizer.Reports/MarkdownReportGenerator.cs
  class MarkdownReportGenerator (line 9) | public class MarkdownReportGenerator
    method MarkdownReportGenerator (line 13) | public MarkdownReportGenerator(BrandingOptions? branding = null)
    method GenerateReport (line 21) | public void GenerateReport(ReportData data, string outputPath)
    method GenerateMarkdown (line 30) | public string GenerateMarkdown(ReportData data)
    method ComposeHeader (line 69) | private void ComposeHeader(StringBuilder sb, ReportData data)
    method ComposeNetworkReference (line 77) | private void ComposeNetworkReference(StringBuilder sb, ReportData data)
    method ComposeExecutiveSummary (line 105) | private void ComposeExecutiveSummary(StringBuilder sb, ReportData data)
    method ComposeActionItems (line 146) | private void ComposeActionItems(StringBuilder sb, ReportData data)
    method ComposeSwitchDetails (line 199) | private void ComposeSwitchDetails(StringBuilder sb, ReportData data)
    method ComposePortSecuritySummary (line 266) | private void ComposePortSecuritySummary(StringBuilder sb, ReportData d...
    method ComposeThreatSummary (line 285) | private void ComposeThreatSummary(StringBuilder sb, ReportData data)
    method ComposeFooter (line 342) | private void ComposeFooter(StringBuilder sb, ReportData data)

FILE: src/NetworkOptimizer.Reports/PdfReportGenerator.cs
  class PdfReportGenerator (line 13) | public class PdfReportGenerator
    method PdfReportGenerator (line 18) | public PdfReportGenerator(BrandingOptions? branding = null)
    method LoadLogoFromResources (line 29) | private void LoadLogoFromResources()
    method MatchesSwitch (line 56) | private static bool MatchesSwitch(AuditIssue issue, SwitchDetail switc...
    method GenerateReport (line 72) | public void GenerateReport(ReportData data, string outputPath)
    method GenerateReportBytes (line 93) | public byte[] GenerateReportBytes(ReportData data)
    method ComposeHeader (line 111) | private void ComposeHeader(IContainer container, ReportData data)
    method ComposeFooter (line 144) | private void ComposeFooter(IContainer container, ReportData data)
    method ComposeContent (line 174) | private void ComposeContent(IContainer container, ReportData data)
    method ComposeNetworkReference (line 225) | private void ComposeNetworkReference(IContainer container, ReportData ...
    method ComposeDnsSecuritySection (line 281) | private void ComposeDnsSecuritySection(IContainer container, ReportDat...
    method ComposeExecutiveSummary (line 434) | private void ComposeExecutiveSummary(IContainer container, ReportData ...
    method ComposeActionItems (line 518) | private void ComposeActionItems(IContainer container, ReportData data)
    method ComposeSwitchDetails (line 648) | private void ComposeSwitchDetails(IContainer container, ReportData data)
    method ComposeAccessPointDetails (line 731) | private void ComposeAccessPointDetails(IContainer container, ReportDat...
    method ComposeOfflineClientsSection (line 868) | private void ComposeOfflineClientsSection(IContainer container, Report...
    method ComposePortTable (line 975) | private void ComposePortTable(IContainer container, SwitchDetail switc...
    method ComposeThreatSummary (line 1096) | private void ComposeThreatSummary(IContainer container, ReportData data)
    method ComposePortSecuritySummary (line 1276) | private void ComposePortSecuritySummary(IContainer container, ReportDa...
    method TrimUniFiSuffix (line 1341) | private static string TrimUniFiSuffix(string message) =>
    method GetColor (line 1344) | private string GetColor(string hexColor)

FILE: src/NetworkOptimizer.Reports/ReportData.cs
  class ReportData (line 8) | public class ReportData
  class DnsSecuritySummary (line 29) | public class DnsSecuritySummary
    method GetDohStatusDisplay (line 58) | public string GetDohStatusDisplay()
    method GetProtectionStatusDisplay (line 63) | public string GetProtectionStatusDisplay()
    method GetWanDnsDisplay (line 69) | public string GetWanDnsDisplay()
    method GetDnsLeakProtectionDetail (line 77) | public string GetDnsLeakProtectionDetail()
    method GetDeviceDnsDisplay (line 101) | public string GetDeviceDnsDisplay()
  class ThirdPartyDnsNetworkInfo (line 117) | public class ThirdPartyDnsNetworkInfo
  class SecurityScore (line 128) | public class SecurityScore
    method CalculateRating (line 142) | public static SecurityRating CalculateRating(int criticalCount, int wa...
  type SecurityRating (line 154) | public enum SecurityRating
  class NetworkInfo (line 165) | public class NetworkInfo
    method GetDisplayName (line 174) | public string GetDisplayName() => VlanId == 1
    method ParsePurpose (line 181) | public static NetworkType ParsePurpose(string? purpose) => purpose?.To...
  type NetworkType (line 193) | public enum NetworkType
  class DeviceInfo (line 207) | public class DeviceInfo
  class AccessPointDetail (line 223) | public class AccessPointDetail
  class WirelessClientDetail (line 239) | public class WirelessClientDetail
  class OfflineClientDetail (line 258) | public class OfflineClientDetail
  class SwitchDetail (line 278) | public class SwitchDetail
  class PortDetail (line 302) | public class PortDetail
    method GetLinkStatus (line 336) | public string GetLinkStatus() => DisplayFormatters.GetLinkStatus(IsUp,...
    method GetPoeStatus (line 338) | public string GetPoeStatus() => DisplayFormatters.GetPoeStatus(PoePowe...
    method GetPortSecurityStatus (line 340) | public string GetPortSecurityStatus() => DisplayFormatters.GetPortSecu...
    method GetIsolationStatus (line 342) | public string GetIsolationStatus() => DisplayFormatters.GetIsolationSt...
    method GetStatus (line 344) | public (string Status, PortStatusType StatusType) GetStatus(bool suppo...
    method GetConnectedDeviceStatus (line 386) | private string? GetConnectedDeviceStatus()
    method IsIoTDeviceOnWrongVlan (line 411) | private bool IsIoTDeviceOnWrongVlan()
  type PortStatusType (line 422) | public enum PortStatusType
  class AuditIssue (line 432) | public class AuditIssue
    method GetDeviceDisplay (line 456) | public string GetDeviceDisplay()
    method GetPortDisplay (line 483) | public string GetPortDisplay()
  type IssueType (line 514) | public enum IssueType
  type IssueSeverity (line 525) | public enum IssueSeverity
  class PortSecuritySummary (line 535) | public class PortSecuritySummary
  class ThreatSummaryData (line 552) | public class ThreatSummaryData
  class ThreatSourceEntry (line 564) | public class ThreatSourceEntry
  class ExposedServiceEntry (line 572) | public class ExposedServiceEntry

FILE: src/NetworkOptimizer.Sqm/BaselineCalculator.cs
  class BaselineCalculator (line 8) | public class BaselineCalculator
    method AddSample (line 16) | public void AddSample(SpeedtestSample sample)
    method AddSample (line 24) | public void AddSample(double downloadSpeed, double uploadSpeed, double...
    method CalculateBaseline (line 42) | public BaselineTable CalculateBaseline()
    method GetBaselineTable (line 94) | public BaselineTable GetBaselineTable() => _baselineTable;
    method LoadBaselineTable (line 99) | public void LoadBaselineTable(BaselineTable table)
    method CalculateBlendedSpeed (line 111) | public double CalculateBlendedSpeed(double measuredSpeed, double basel...
    method GetLearningProgress (line 130) | public double GetLearningProgress()
    method IsLearningComplete (line 138) | public bool IsLearningComplete()
    method GetCurrentBaselineSpeed (line 146) | public int? GetCurrentBaselineSpeed()
    method GetBaselineSpeed (line 155) | public int? GetBaselineSpeed(DateTime time)
    method UpdateHourlyBaseline (line 164) | public void UpdateHourlyBaseline(SpeedtestSample sample)
    method ExportToShellFormat (line 208) | public Dictionary<string, string> ExportToShellFormat()
    method ImportFromShellFormat (line 223) | public void ImportFromShellFormat(Dictionary<string, string> shellBase...
    method CalculateMedian (line 262) | private static double CalculateMedian(List<double> sortedValues)
    method GetDayOfWeek (line 281) | private static int GetDayOfWeek(DateTime time)

FILE: src/NetworkOptimizer.Sqm/InputSanitizer.cs
  class InputSanitizer (line 10) | public static partial class InputSanitizer
    method ValidatePingHost (line 16) | public static (bool isValid, string? error) ValidatePingHost(string? p...
    method ValidateSpeedtestServerId (line 63) | public static (bool isValid, string? error) ValidateSpeedtestServerId(...
    method SanitizeConnectionName (line 88) | public static string SanitizeConnectionName(string? connectionName)
    method ValidateCronSchedule (line 126) | public static (bool isValid, string? error) ValidateCronSchedule(strin...
    method IsValidCronField (line 173) | private static bool IsValidCronField(string field, int min, int max)
    method ValidateInterface (line 217) | public static (bool isValid, string? error) ValidateInterface(string? ...
    method EscapeForShellDoubleQuote (line 242) | public static string EscapeForShellDoubleQuote(string value)
    method TrimPingHost (line 261) | public static string? TrimPingHost(string? pingHost)
    method TrimSpeedtestServerId (line 267) | public static string? TrimSpeedtestServerId(string? serverId)
    method TrimInterface (line 273) | public static string? TrimInterface(string? interfaceName)
    method HostnameRegex (line 277) | [GeneratedRegex(@"^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$")]
    method NumericRegex (line 280) | [GeneratedRegex(@"^[0-9]+$")]
    method SafeNameRegex (line 283) | [GeneratedRegex(@"[^a-z0-9\-]")]
    method MultipleHyphensRegex (line 286) | [GeneratedRegex(@"-+")]
    method InterfaceRegex (line 289) | [GeneratedRegex(@"^[a-zA-Z0-9_\-\.]+$")]

FILE: src/NetworkOptimizer.Sqm/LatencyMonitor.cs
  class LatencyMonitor (line 8) | public class LatencyMonitor
    method LatencyMonitor (line 12) | public LatencyMonitor(SqmConfiguration config)
    method CalculateRateAdjustment (line 24) | public (double adjustedRate, string reason) CalculateRateAdjustment(
    method IsLatencyHigh (line 112) | public bool IsLatencyHigh(double currentLatency)
    method CalculateDeviationCount (line 120) | public int CalculateDeviationCount(double currentLatency)
    method GeneratePingCommand (line 130) | public string GeneratePingCommand()
    method ParsePingOutput (line 140) | public double? ParsePingOutput(string pingOutput)
    method CalculateDecreaseMultiplier (line 167) | public double CalculateDecreaseMultiplier(int deviations)
    method CalculateIncreaseMultiplier (line 175) | public double CalculateIncreaseMultiplier(int steps = 1)
    method CapRate (line 183) | private double CapRate(double rate)
    method NeedsRecovery (line 198) | public bool NeedsRecovery(double currentRate)
    method GetRateBounds (line 207) | public (double minRate, double optimalRate, double maxRate) GetRateBou...

FILE: src/NetworkOptimizer.Sqm/Models/BaselineData.cs
  class HourlyBaseline (line 6) | public class HourlyBaseline
  class BaselineTable (line 57) | public class BaselineTable
    method GetBaseline (line 82) | public HourlyBaseline? GetBaseline(DateTime time)
    method GetCurrentBaseline (line 93) | public HourlyBaseline? GetCurrentBaseline()
    method GetDayOfWeek (line 101) | private static int GetDayOfWeek(DateTime time)
    method GetCompletionPercentage (line 111) | public double GetCompletionPercentage()
  class SpeedtestSample (line 120) | public class SpeedtestSample

FILE: src/NetworkOptimizer.Sqm/Models/ConnectionProfile.cs
  type ConnectionType (line 6) | public enum ConnectionType
  class ConnectionProfile (line 33) | public class ConnectionProfile
    method GetDefaultSpeedtestServer (line 130) | private string? GetDefaultSpeedtestServer()
    method CalculateMaxSpeed (line 144) | private int CalculateMaxSpeed(int nominalSpeed)
    method CalculateMinSpeed (line 176) | private int CalculateMinSpeed(int nominalSpeed)
    method CalculateAbsoluteMax (line 208) | private int CalculateAbsoluteMax(int nominalSpeed)
    method GetOverheadMultiplier (line 226) | private double GetOverheadMultiplier()
    method GetBaselineLatency (line 258) | private double GetBaselineLatency()
    method GetLatencyThreshold (line 276) | private double GetLatencyThreshold()
    method GetLatencyDecrease (line 308) | private double GetLatencyDecrease()
    method GetLatencyIncrease (line 326) | private double GetLatencyIncrease()
    method GetSafetyCapPercent (line 345) | private double GetSafetyCapPercent()
    method GetHourlyBaseline (line 366) | public Dictionary<string, string> GetHourlyBaseline(double congestionS...
    method GetBlendingRatios (line 396) | public (double baselineWeight, double measuredWeight) GetBlendingRatio...
    method GetBaselinePatternPublic (line 437) | public double[,] GetBaselinePatternPublic() => GetBaselinePattern();
    method GetBaselinePattern (line 444) | private double[,] GetBaselinePattern()
    method CreateUniformWeekPattern (line 540) | private static double[,] CreateUniformWeekPattern(double[] dailyPattern)
    method ToSqmConfiguration (line 556) | public SqmConfiguration ToSqmConfiguration()
    method GetConnectionTypeName (line 576) | public static string GetConnectionTypeName(ConnectionType type)
    method GetConnectionTypeDescription (line 594) | public static string GetConnectionTypeDescription(ConnectionType type)

FILE: src/NetworkOptimizer.Sqm/Models/SpeedtestResult.cs
  class SpeedtestResult (line 8) | public class SpeedtestResult
  class PingInfo (line 41) | public class PingInfo
  class BandwidthInfo (line 56) | public class BandwidthInfo
  class LatencyInfo (line 83) | public class LatencyInfo
  class InterfaceInfo (line 98) | public class InterfaceInfo
  class ServerInfo (line 116) | public class ServerInfo
  class ResultInfo (line 140) | public class ResultInfo

FILE: src/NetworkOptimizer.Sqm/Models/SqmConfiguration.cs
  class SqmConfiguration (line 6) | public class SqmConfiguration
    method GetProfile (line 176) | public ConnectionProfile GetProfile()
    method ApplyProfileSettings (line 194) | public void ApplyProfileSettings(int? wanLinkSpeedMbps = null)
    method FromProfile (line 237) | public static SqmConfiguration FromProfile(ConnectionProfile profile)
    method GetParameterSummary (line 268) | public string GetParameterSummary()

FILE: src/NetworkOptimizer.Sqm/Models/SqmStatus.cs
  class SqmStatus (line 6) | public class SqmStatus

FILE: src/NetworkOptimizer.Sqm/ScriptGenerator.cs
  class ScriptGenerator (line 11) | public class ScriptGenerator
    method ScriptGenerator (line 17) | public ScriptGenerator(SqmConfiguration config, int initialDelaySecond...
    method Inv (line 33) | private static string Inv(double value) => Math.Round(value, 10).ToStr...
    method GenerateAllScripts (line 39) | public Dictionary<string, string> GenerateAllScripts(Dictionary<string...
    method GetBootScriptName (line 50) | public string GetBootScriptName() => $"20-sqm-{_name}.sh";
    method GenerateBootScript (line 60) | public string GenerateBootScript(Dictionary<string, string> baseline)
    method GenerateSpeedtestScript (line 220) | private string GenerateSpeedtestScript(Dictionary<string, string> base...
    method GeneratePingScript (line 361) | private string GeneratePingScript(Dictionary<string, string> baseline)
    method GetTcUpdateFunction (line 573) | private string GetTcUpdateFunction()
    method GetBaselineBlendingLogic (line 698) | private string GetBaselineBlendingLogic()
    method GetBaselineBlendingLogicForPing (line 755) | private string GetBaselineBlendingLogicForPing()
    method GetLatencyAdjustmentLogic (line 802) | private string GetLatencyAdjustmentLogic()

FILE: src/NetworkOptimizer.Sqm/SpeedtestIntegration.cs
  class SpeedtestIntegration (line 9) | public class SpeedtestIntegration
    method SpeedtestIntegration (line 13) | public SpeedtestIntegration(SqmConfiguration config)
    method ParseSpeedtestJson (line 21) | public SpeedtestResult? ParseSpeedtestJson(string json)
    method BytesPerSecToMbps (line 36) | public double BytesPerSecToMbps(long bytesPerSec)
    method CalculateEffectiveRate (line 46) | public double CalculateEffectiveRate(double downloadMbps)
    method ProcessSpeedtestResult (line 63) | public double ProcessSpeedtestResult(SpeedtestResult result, BaselineC...
    method CreateSample (line 106) | public SpeedtestSample CreateSample(SpeedtestResult result)
    method IsValidResult (line 126) | public bool IsValidResult(SpeedtestResult result)
    method CalculateVariancePercent (line 143) | public double CalculateVariancePercent(double measuredSpeed, double ba...
    method DetermineBlendRatio (line 154) | public (double baselineWeight, double measuredWeight) DetermineBlendRa...
    method GetDayOfWeek (line 171) | private static int GetDayOfWeek(DateTime time)

FILE: src/NetworkOptimizer.Sqm/SqmManager.cs
  class SqmManager (line 8) | public class SqmManager
    method SqmManager (line 18) | public SqmManager(SqmConfiguration config)
    method ConfigureSqm (line 30) | public void ConfigureSqm(SqmConfiguration config)
    method StartLearningMode (line 48) | public void StartLearningMode()
    method StopLearningMode (line 60) | public void StopLearningMode()
    method GetStatus (line 73) | public SqmStatus GetStatus()
    method TriggerSpeedtest (line 90) | public async Task<double> TriggerSpeedtest(string speedtestJsonOutput)
    method ApplyRateAdjustment (line 124) | public (double adjustedRate, string reason) ApplyRateAdjustment(double...
    method LoadBaseline (line 145) | public void LoadBaseline(BaselineTable baseline)
    method GetBaselineTable (line 153) | public BaselineTable GetBaselineTable()
    method ExportBaselineForScript (line 161) | public Dictionary<string, string> ExportBaselineForScript()
    method GenerateScripts (line 169) | public Dictionary<string, string> GenerateScripts()
    method GenerateScriptsToDirectory (line 178) | public void GenerateScriptsToDirectory(string outputDirectory)
    method IsLearningComplete (line 194) | public bool IsLearningComplete()
    method GetLearningProgress (line 202) | public double GetLearningProgress()
    method GetRateBounds (line 210) | public (double minRate, double optimalRate, double maxRate) GetRateBou...
    method ValidateConfiguration (line 218) | public List<string> ValidateConfiguration()

FILE: src/NetworkOptimizer.Storage/Helpers/SpeedTestFilterHelper.cs
  class SpeedTestFilterHelper (line 10) | public static class SpeedTestFilterHelper
    method MatchesFilter (line 26) | public static bool MatchesFilter(Iperf3Result result, string normalize...
    method MatchesHop (line 68) | private static bool MatchesHop(NetworkHop hop, string normalizedFilter)

FILE: src/NetworkOptimizer.Storage/InfluxDbStorage.cs
  class InfluxDbStorage (line 13) | public class InfluxDbStorage : IMetricsStorage, IDisposable, IAsyncDispo...
    method InfluxDbStorage (line 28) | public InfluxDbStorage(
    method RunFlushLoopAsync (line 76) | private async Task RunFlushLoopAsync(CancellationToken cancellationToken)
    method WriteMetricsAsync (line 99) | public async Task WriteMetricsAsync(
    method WriteInterfaceMetricsAsync (line 163) | public async Task WriteInterfaceMetricsAsync(
    method WriteSqmMetricsAsync (line 228) | public async Task WriteSqmMetricsAsync(
    method HealthCheckAsync (line 284) | public async Task<bool> HealthCheckAsync(CancellationToken cancellatio...
    method WritePointAsync (line 301) | private async Task WritePointAsync(PointData point, CancellationToken ...
    method FlushBufferAsync (line 324) | private async Task FlushBufferAsync()
    method ForceFlushAsync (line 366) | public async Task ForceFlushAsync()
    method DisposeAsync (line 377) | public async ValueTask DisposeAsync()
    method Dispose (line 418) | public void Dispose()

FILE: src/NetworkOptimizer.Storage/Interfaces/IAgentRepository.cs
  type IAgentRepository (line 8) | public interface IAgentRepository
    method SaveAgentConfigAsync (line 10) | Task<int> SaveAgentConfigAsync(AgentConfiguration config, Cancellation...
    method GetAgentConfigAsync (line 11) | Task<AgentConfiguration?> GetAgentConfigAsync(string agentId, Cancella...
    method GetAllAgentConfigsAsync (line 12) | Task<List<AgentConfiguration>> GetAllAgentConfigsAsync(CancellationTok...
    method UpdateAgentConfigAsync (line 13) | Task UpdateAgentConfigAsync(AgentConfiguration config, CancellationTok...
    method DeleteAgentConfigAsync (line 14) | Task DeleteAgentConfigAsync(string agentId, CancellationToken cancella...

FILE: src/NetworkOptimizer.Storage/Interfaces/IAuditRepository.cs
  type IAuditRepository (line 8) | public interface IAuditRepository
    method SaveAuditResultAsync (line 11) | Task<int> SaveAuditResultAsync(AuditResult audit, CancellationToken ca...
    method GetAuditResultAsync (line 12) | Task<AuditResult?> GetAuditResultAsync(int auditId, CancellationToken ...
    method GetLatestAuditResultAsync (line 13) | Task<AuditResult?> GetLatestAuditResultAsync(CancellationToken cancell...
    method GetAuditHistoryAsync (line 14) | Task<List<AuditResult>> GetAuditHistoryAsync(string? deviceId = null, ...
    method GetAuditCountAsync (line 15) | Task<int> GetAuditCountAsync(CancellationToken cancellationToken = def...
    method GetManualAuditCountAsync (line 16) | Task<int> GetManualAuditCountAsync(CancellationToken cancellationToken...
    method GetScheduledAuditCountAsync (line 17) | Task<int> GetScheduledAuditCountAsync(CancellationToken cancellationTo...
    method DeleteOldAuditsAsync (line 18) | Task DeleteOldAuditsAsync(DateTime olderThan, CancellationToken cancel...
    method ClearAllAuditsAsync (line 19) | Task ClearAllAuditsAsync(CancellationToken cancellationToken = default);
    method GetDismissedIssuesAsync (line 22) | Task<List<DismissedIssue>> GetDismissedIssuesAsync(CancellationToken c...
    method SaveDismissedIssueAsync (line 23) | Task SaveDismissedIssueAsync(DismissedIssue issue, CancellationToken c...
    method DeleteDismissedIssueAsync (line 24) | Task DeleteDismissedIssueAsync(string issueKey, CancellationToken canc...
    method ClearAllDismissedIssuesAsync (line 25) | Task ClearAllDismissedIssuesAsync(CancellationToken cancellationToken ...

FILE: src/NetworkOptimizer.Storage/Interfaces/IMetricsStorage.cs
  type IMetricsStorage (line 3) | public interface IMetricsStorage
    method WriteMetricsAsync (line 5) | Task WriteMetricsAsync(string deviceId, string measurementType, Dictio...
    method WriteInterfaceMetricsAsync (line 6) | Task WriteInterfaceMetricsAsync(string deviceId, string interfaceId, D...
    method WriteSqmMetricsAsync (line 7) | Task WriteSqmMetricsAsync(string deviceId, Dictionary<string, object> ...
    method HealthCheckAsync (line 8) | Task<bool> HealthCheckAsync(CancellationToken cancellationToken = defa...

FILE: src/NetworkOptimizer.Storage/Interfaces/IModemRepository.cs
  type IModemRepository (line 8) | public interface IModemRepository
    method GetModemConfigurationsAsync (line 10) | Task<List<ModemConfiguration>> GetModemConfigurationsAsync(Cancellatio...
    method GetEnabledModemConfigurationsAsync (line 11) | Task<List<ModemConfiguration>> GetEnabledModemConfigurationsAsync(Canc...
    method GetModemConfigurationAsync (line 12) | Task<ModemConfiguration?> GetModemConfigurationAsync(int id, Cancellat...
    method SaveModemConfigurationAsync (line 13) | Task SaveModemConfigurationAsync(ModemConfiguration config, Cancellati...
    method DeleteModemConfigurationAsync (line 14) | Task DeleteModemConfigurationAsync(int id, CancellationToken cancellat...

FILE: src/NetworkOptimizer.Storage/Interfaces/ISettingsRepository.cs
  type ISettingsRepository (line 8) | public interface ISettingsRepository
    method GetSystemSettingAsync (line 11) | Task<string?> GetSystemSettingAsync(string key, CancellationToken canc...
    method SaveSystemSettingAsync (line 12) | Task SaveSystemSettingAsync(string key, string? value, CancellationTok...
    method SaveLicenseAsync (line 15) | Task<int> SaveLicenseAsync(LicenseInfo license, CancellationToken canc...
    method GetLicenseAsync (line 16) | Task<LicenseInfo?> GetLicenseAsync(CancellationToken cancellationToken...
    method UpdateLicenseAsync (line 17) | Task UpdateLicenseAsync(LicenseInfo license, CancellationToken cancell...
    method GetAdminSettingsAsync (line 20) | Task<AdminSettings?> GetAdminSettingsAsync(CancellationToken cancellat...
    method SaveAdminSettingsAsync (line 21) | Task SaveAdminSettingsAsync(AdminSettings settings, CancellationToken ...

FILE: src/NetworkOptimizer.Storage/Interfaces/ISpeedTestRepository.cs
  type ISpeedTestRepository (line 12) | public interface ISpeedTestRepository
    method GetGatewaySshSettingsAsync (line 15) | Task<GatewaySshSettings?> GetGatewaySshSettingsAsync(CancellationToken...
    method SaveGatewaySshSettingsAsync (line 16) | Task SaveGatewaySshSettingsAsync(GatewaySshSettings settings, Cancella...
    method SaveIperf3ResultAsync (line 19) | Task SaveIperf3ResultAsync(Iperf3Result result, CancellationToken canc...
    method GetRecentIperf3ResultsAsync (line 20) | Task<List<Iperf3Result>> GetRecentIperf3ResultsAsync(int count = 50, i...
    method GetIperf3ResultsForDeviceAsync (line 21) | Task<List<Iperf3Result>> GetIperf3ResultsForDeviceAsync(string deviceH...
    method SearchIperf3ResultsAsync (line 31) | Task<List<Iperf3Result>> SearchIperf3ResultsAsync(string filter, int c...
    method DeleteIperf3ResultAsync (line 33) | Task<bool> DeleteIperf3ResultAsync(int id, CancellationToken cancellat...
    method ClearIperf3HistoryAsync (line 34) | Task ClearIperf3HistoryAsync(CancellationToken cancellationToken = def...
    method ClearAllIperf3ResultsAsync (line 35) | Task ClearAllIperf3ResultsAsync(CancellationToken cancellationToken = ...
    method GetIperf3ResultCountAsync (line 36) | Task<int> GetIperf3ResultCountAsync(CancellationToken cancellationToke...
    method UpdateIperf3ResultNotesAsync (line 45) | Task<bool> UpdateIperf3ResultNotesAsync(int id, string? notes, Cancell...
    method GetSqmWanConfigAsync (line 48) | Task<SqmWanConfiguration?> GetSqmWanConfigAsync(int wanNumber, Cancell...
    method GetAllSqmWanConfigsAsync (line 49) | Task<List<SqmWanConfiguration>> GetAllSqmWanConfigsAsync(CancellationT...
    method SaveSqmWanConfigAsync (line 50) | Task SaveSqmWanConfigAsync(SqmWanConfiguration config, CancellationTok...
    method DeleteSqmWanConfigAsync (line 51) | Task DeleteSqmWanConfigAsync(int wanNumber, CancellationToken cancella...
    method ClearAllSqmWanConfigsAsync (line 52) | Task ClearAllSqmWanConfigsAsync(CancellationToken cancellationToken = ...

FILE: src/NetworkOptimizer.Storage/Interfaces/ISqmRepository.cs
  type ISqmRepository (line 8) | public interface ISqmRepository
    method SaveSqmBaselineAsync (line 10) | Task<int> SaveSqmBaselineAsync(SqmBaseline baseline, CancellationToken...
    method GetSqmBaselineAsync (line 11) | Task<SqmBaseline?> GetSqmBaselineAsync(string deviceId, string interfa...
    method GetAllSqmBaselinesAsync (line 12) | Task<List<SqmBaseline>> GetAllSqmBaselinesAsync(string? deviceId = nul...
    method UpdateSqmBaselineAsync (line 13) | Task UpdateSqmBaselineAsync(SqmBaseline baseline, CancellationToken ca...
    method DeleteSqmBaselineAsync (line 14) | Task DeleteSqmBaselineAsync(int baselineId, CancellationToken cancella...

FILE: src/NetworkOptimizer.Storage/Interfaces/IUniFiRepository.cs
  type IUniFiRepository (line 8) | public interface IUniFiRepository
    method GetUniFiConnectionSettingsAsync (line 11) | Task<UniFiConnectionSettings?> GetUniFiConnectionSettingsAsync(Cancell...
    method SaveUniFiConnectionSettingsAsync (line 12) | Task SaveUniFiConnectionSettingsAsync(UniFiConnectionSettings settings...
    method GetUniFiSshSettingsAsync (line 15) | Task<UniFiSshSettings?> GetUniFiSshSettingsAsync(CancellationToken can...
    method SaveUniFiSshSettingsAsync (line 16) | Task SaveUniFiSshSettingsAsync(UniFiSshSettings settings, Cancellation...
    method GetDeviceSshConfigurationsAsync (line 19) | Task<List<DeviceSshConfiguration>> GetDeviceSshConfigurationsAsync(Can...
    method GetDeviceSshConfigurationAsync (line 20) | Task<DeviceSshConfiguration?> GetDeviceSshConfigurationAsync(int id, C...
    method SaveDeviceSshConfigurationAsync (line 21) | Task SaveDeviceSshConfigurationAsync(DeviceSshConfiguration config, Ca...
    method DeleteDeviceSshConfigurationAsync (line 22) | Task DeleteDeviceSshConfigurationAsync(int id, CancellationToken cance...

FILE: src/NetworkOptimizer.Storage/Migrations/20251208000000_InitialCreate.Designer.cs
  class InitialCreate (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251208000000_InitialCreate.cs
  class InitialCreate (line 8) | public partial class InitialCreate : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 192) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251210000000_AddModemAndSpeedTables.Designer.cs
  class AddModemAndSpeedTables (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251210000000_AddModemAndSpeedTables.cs
  class AddModemAndSpeedTables (line 8) | public partial class AddModemAndSpeedTables : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 123) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251216000000_AddUniFiSshSettings.Designer.cs
  class AddUniFiSshSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251216000000_AddUniFiSshSettings.cs
  class AddUniFiSshSettings (line 8) | public partial class AddUniFiSshSettings : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 39) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217000000_AddDismissedIssues.Designer.cs
  class AddDismissedIssues (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217000000_AddDismissedIssues.cs
  class AddDismissedIssues (line 8) | public partial class AddDismissedIssues : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 35) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217100000_AddGatewaySshSettings.Designer.cs
  class AddGatewaySshSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217100000_AddGatewaySshSettings.cs
  class AddGatewaySshSettings (line 8) | public partial class AddGatewaySshSettings : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 38) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217200000_AddStartIperf3ServerToDeviceConfig.Designer.cs
  class AddStartIperf3ServerToDeviceConfig (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217200000_AddStartIperf3ServerToDeviceConfig.cs
  class AddStartIperf3ServerToDeviceConfig (line 8) | public partial class AddStartIperf3ServerToDeviceConfig : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217300000_AddSystemSettings.Designer.cs
  class AddSystemSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251217300000_AddSystemSettings.cs
  class AddSystemSettings (line 8) | public partial class AddSystemSettings : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 35) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251218000000_AddSshCredentialOverridesToDeviceConfig.Designer.cs
  class AddSshCredentialOverridesToDeviceConfig (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251218000000_AddSshCredentialOverridesToDeviceConfig.cs
  class AddSshCredentialOverridesToDeviceConfig (line 8) | public partial class AddSshCredentialOverridesToDeviceConfig : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 36) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251219000000_AddUniFiConnectionSettings.Designer.cs
  class AddUniFiConnectionSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251219000000_AddUniFiConnectionSettings.cs
  class AddUniFiConnectionSettings (line 8) | public partial class AddUniFiConnectionSettings : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 37) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251224000000_AddPathAnalysisJson.Designer.cs
  class AddPathAnalysisJson (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251224000000_AddPathAnalysisJson.cs
  class AddPathAnalysisJson (line 8) | public partial class AddPathAnalysisJson : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 21) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251227000000_AddTcMonitorPort.Designer.cs
  class AddTcMonitorPort (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251227000000_AddTcMonitorPort.cs
  class AddTcMonitorPort (line 8) | public partial class AddTcMonitorPort : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251227100000_AddSqmWanConfiguration.Designer.cs
  class AddSqmWanConfiguration (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251227100000_AddSqmWanConfiguration.cs
  class AddSqmWanConfiguration (line 8) | public partial class AddSqmWanConfiguration : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 44) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251228000000_AddAdminSettings.Designer.cs
  class AddAdminSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251228000000_AddAdminSettings.cs
  class AddAdminSettings (line 8) | public partial class AddAdminSettings : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 31) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251228100000_AddSqmSpeedtestSchedule.Designer.cs
  class AddSqmSpeedtestSchedule (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251228100000_AddSqmSpeedtestSchedule.cs
  class AddSqmSpeedtestSchedule (line 8) | public partial class AddSqmSpeedtestSchedule : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 43) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251229000000_AddReportDataJson.Designer.cs
  class AddReportDataJson (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20251229000000_AddReportDataJson.cs
  class AddReportDataJson (line 8) | public partial class AddReportDataJson : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 21) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260102000000_AddLocalIpToIperf3Result.Designer.cs
  class AddLocalIpToIperf3Result (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260102000000_AddLocalIpToIperf3Result.cs
  class AddLocalIpToIperf3Result (line 8) | public partial class AddLocalIpToIperf3Result : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260103000000_AddIgnoreControllerSSLErrors.Designer.cs
  class AddIgnoreControllerSSLErrors (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260103000000_AddIgnoreControllerSSLErrors.cs
  class AddIgnoreControllerSSLErrors (line 8) | public partial class AddIgnoreControllerSSLErrors : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260104100000_AddClientSpeedTestFieldsToIperf3Result.Designer.cs
  class AddClientSpeedTestFieldsToIperf3Result (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260104100000_AddClientSpeedTestFieldsToIperf3Result.cs
  class AddClientSpeedTestFieldsToIperf3Result (line 8) | public partial class AddClientSpeedTestFieldsToIperf3Result : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 53) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260106000000_AddLocationAndWifiSignal.Designer.cs
  class AddLocationAndWifiSignal (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260106000000_AddLocationAndWifiSignal.cs
  class AddLocationAndWifiSignal (line 8) | public partial class AddLocationAndWifiSignal : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 60) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107000000_AddWifiRadio.Designer.cs
  class AddWifiRadio (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107000000_AddWifiRadio.cs
  class AddWifiRadio (line 8) | public partial class AddWifiRadio : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107100000_AddWifiMlo.Designer.cs
  class AddWifiMlo (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107100000_AddWifiMlo.cs
  class AddWifiMlo (line 8) | public partial class AddWifiMlo : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 28) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107200000_AddWifiTxRxRates.Designer.cs
  class AddWifiTxRxRates (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260107200000_AddWifiTxRxRates.cs
  class AddWifiTxRxRates (line 8) | public partial class AddWifiTxRxRates : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 27) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260110000000_AddIperf3BinaryPathToDeviceConfig.Designer.cs
  class AddIperf3BinaryPathToDeviceConfig (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260110000000_AddIperf3BinaryPathToDeviceConfig.cs
  class AddIperf3BinaryPathToDeviceConfig (line 8) | public partial class AddIperf3BinaryPathToDeviceConfig : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260113000000_AddUpnpNotes.Designer.cs
  class AddUpnpNotes (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260113000000_AddUpnpNotes.cs
  class AddUpnpNotes (line 8) | public partial class AddUpnpNotes : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 39) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260124000000_AddNotesToIperf3Result.Designer.cs
  class AddNotesToIperf3Result (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260124000000_AddNotesToIperf3Result.cs
  class AddNotesToIperf3Result (line 8) | public partial class AddNotesToIperf3Result : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260209200000_AddApLocations.Designer.cs
  class AddApLocations (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260209200000_AddApLocations.cs
  class AddApLocations (line 9) | public partial class AddApLocations : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 39) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260210100000_AddLoadedLatencyColumns.Designer.cs
  class AddLoadedLatencyColumns (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260210100000_AddLoadedLatencyColumns.cs
  class AddLoadedLatencyColumns (line 8) | public partial class AddLoadedLatencyColumns : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 39) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211000000_AddWanIdentityColumns.Designer.cs
  class AddWanIdentityColumns (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211000000_AddWanIdentityColumns.cs
  class AddWanIdentityColumns (line 8) | public partial class AddWanIdentityColumns : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 29) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211200000_AddBuildingsAndFloorPlans.Designer.cs
  class AddBuildingsAndFloorPlans (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211200000_AddBuildingsAndFloorPlans.cs
  class AddBuildingsAndFloorPlans (line 9) | public partial class AddBuildingsAndFloorPlans : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 67) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211300000_AddApOrientationDeg.Designer.cs
  class AddApOrientationDeg (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211300000_AddApOrientationDeg.cs
  class AddApOrientationDeg (line 8) | public partial class AddApOrientationDeg : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211400000_AddApMountType.Designer.cs
  class AddApMountType (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260211400000_AddApMountType.cs
  class AddApMountType (line 8) | public partial class AddApMountType : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260212000000_AddFloorMaterial.Designer.cs
  class AddFloorMaterial (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260212000000_AddFloorMaterial.cs
  class AddFloorMaterial (line 8) | public partial class AddFloorMaterial : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 23) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260213000000_AddClientSignalLog.Designer.cs
  class AddClientSignalLog (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260213000000_AddClientSignalLog.cs
  class AddClientSignalLog (line 9) | public partial class AddClientSignalLog : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 70) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260213000000_AddPlannedAps.Designer.cs
  class AddPlannedAps (line 11) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 16) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260213000000_AddPlannedAps.cs
  class AddPlannedAps (line 9) | public partial class AddPlannedAps : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 39) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260214100000_AddPerBandTxPower.Designer.cs
  class AddPerBandTxPower (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260214100000_AddPerBandTxPower.cs
  class AddPerBandTxPower (line 8) | public partial class AddPerBandTxPower : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 60) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260220000000_AddFloorPlanImages.Designer.cs
  class AddFloorPlanImages (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260220000000_AddFloorPlanImages.cs
  class AddFloorPlanImages (line 8) | public partial class AddFloorPlanImages : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 51) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260221000000_AddAlertTables.Designer.cs
  class AddAlertTables (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260221000000_AddAlertTables.cs
  class AddAlertTables (line 9) | public partial class AddAlertTables : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 146) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260221100000_AddThreatTables.Designer.cs
  class AddThreatTables (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260221100000_AddThreatTables.cs
  class AddThreatTables (line 9) | public partial class AddThreatTables : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 130) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260222100000_AddTrafficFlowFields.Designer.cs
  class AddTrafficFlowFields (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260222100000_AddTrafficFlowFields.cs
  class AddTrafficFlowFields (line 8) | public partial class AddTrafficFlowFields : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 69) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260222200000_AddThreatNoiseFilters.Designer.cs
  class AddThreatNoiseFilters (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260222200000_AddThreatNoiseFilters.cs
  class AddThreatNoiseFilters (line 9) | public partial class AddThreatNoiseFilters : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 34) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260223000000_AddScheduledTasks.Designer.cs
  class AddScheduledTasks (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260223000000_AddScheduledTasks.cs
  class AddScheduledTasks (line 9) | public partial class AddScheduledTasks : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 59) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260223100000_AddAlertRuleThreshold.Designer.cs
  class AddAlertRuleThreshold (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260223100000_AddAlertRuleThreshold.cs
  class AddAlertRuleThreshold (line 7) | public partial class AddAlertRuleThreshold : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260225200000_AddPatternLastAlertedAt.Designer.cs
  class AddPatternLastAlertedAt (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260225200000_AddPatternLastAlertedAt.cs
  class AddPatternLastAlertedAt (line 7) | public partial class AddPatternLastAlertedAt : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226010000_AddPatternDedupKey.Designer.cs
  class AddPatternDedupKey (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226010000_AddPatternDedupKey.cs
  class AddPatternDedupKey (line 7) | public partial class AddPatternDedupKey : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226100000_AddAuditIsScheduled.Designer.cs
  class AddAuditIsScheduled (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226100000_AddAuditIsScheduled.cs
  class AddAuditIsScheduled (line 7) | public partial class AddAuditIsScheduled : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 19) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226120000_AddSqmBaselineLatency.Designer.cs
  class AddSqmBaselineLatency (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260226120000_AddSqmBaselineLatency.cs
  class AddSqmBaselineLatency (line 7) | public partial class AddSqmBaselineLatency : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260228000000_AddWanDataUsageTables.Designer.cs
  class AddWanDataUsageTables (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260228000000_AddWanDataUsageTables.cs
  class AddWanDataUsageTables (line 9) | public partial class AddWanDataUsageTables : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 66) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260301000000_AddSignalLogChannelWidth.Designer.cs
  class AddSignalLogChannelWidth (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260301000000_AddSignalLogChannelWidth.cs
  class AddSignalLogChannelWidth (line 8) | public partial class AddSignalLogChannelWidth : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 21) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260301200000_AddSnapshotGatewayBootTime.Designer.cs
  class AddSnapshotGatewayBootTime (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260301200000_AddSnapshotGatewayBootTime.cs
  class AddSnapshotGatewayBootTime (line 9) | public partial class AddSnapshotGatewayBootTime : Migration
    method Up (line 12) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 22) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260306000000_AddAlertSourceUrl.Designer.cs
  class AddAlertSourceUrl (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260306000000_AddAlertSourceUrl.cs
  class AddAlertSourceUrl (line 7) | public partial class AddAlertSourceUrl : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260311000000_AddDeviceIperf3Overrides.Designer.cs
  class AddDeviceIperf3Overrides (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260311000000_AddDeviceIperf3Overrides.cs
  class AddDeviceIperf3Overrides (line 8) | public partial class AddDeviceIperf3Overrides : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 27) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260312000000_PurgeStaleCrowdSecNegativeCache.Designer.cs
  class PurgeStaleCrowdSecNegativeCache (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260312000000_PurgeStaleCrowdSecNegativeCache.cs
  class PurgeStaleCrowdSecNegativeCache (line 14) | public partial class PurgeStaleCrowdSecNegativeCache : Migration
    method Up (line 17) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 23) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260318000000_AddExternalServerName.Designer.cs
  class AddExternalServerName (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260318000000_AddExternalServerName.cs
  class AddExternalServerName (line 11) | public partial class AddExternalServerName : Migration
    method Up (line 14) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 25) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260320000000_AddWanSteerTrafficClasses.Designer.cs
  class AddWanSteerTrafficClasses (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260320000000_AddWanSteerTrafficClasses.cs
  class AddWanSteerTrafficClasses (line 8) | public partial class AddWanSteerTrafficClasses : Migration
    method Up (line 11) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 43) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260402100000_AddCongestionSeverity.Designer.cs
  class AddCongestionSeverity (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260402100000_AddCongestionSeverity.cs
  class AddCongestionSeverity (line 7) | public partial class AddCongestionSeverity : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 25) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260404000000_AddApiKey.Designer.cs
  class AddApiKey (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260404000000_AddApiKey.cs
  class AddApiKey (line 7) | public partial class AddApiKey : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 19) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260405000000_AddExternalSpeedTestServers.Designer.cs
  class AddExternalSpeedTestServers (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260405000000_AddExternalSpeedTestServers.cs
  class AddExternalSpeedTestServers (line 11) | public partial class AddExternalSpeedTestServers : Migration
    method Up (line 13) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 66) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260428000000_AddSqmLinkSpeedOverride.Designer.cs
  class AddSqmLinkSpeedOverride (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260428000000_AddSqmLinkSpeedOverride.cs
  class AddSqmLinkSpeedOverride (line 7) | public partial class AddSqmLinkSpeedOverride : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260505000000_AddSqmBootDelay.Designer.cs
  class AddSqmBootDelay (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260505000000_AddSqmBootDelay.cs
  class AddSqmBootDelay (line 7) | public partial class AddSqmBootDelay : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 18) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260507000000_AddPerfTweakSettings.Designer.cs
  class AddPerfTweakSettings (line 13) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildTargetModel (line 18) | protected override void BuildTargetModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/20260507000000_AddPerfTweakSettings.cs
  class AddPerfTweakSettings (line 7) | public partial class AddPerfTweakSettings : Migration
    method Up (line 9) | protected override void Up(MigrationBuilder migrationBuilder)
    method Down (line 32) | protected override void Down(MigrationBuilder migrationBuilder)

FILE: src/NetworkOptimizer.Storage/Migrations/NetworkOptimizerDbContextModelSnapshot.cs
  class NetworkOptimizerDbContextModelSnapshot (line 9) | [DbContext(typeof(NetworkOptimizerDbContext))]
    method BuildModel (line 12) | protected override void BuildModel(ModelBuilder modelBuilder)

FILE: src/NetworkOptimizer.Storage/Models/AdminSettings.cs
  class AdminSettings (line 10) | public class AdminSettings

FILE: src/NetworkOptimizer.Storage/Models/AgentConfiguration.cs
  class AgentConfiguration (line 8) | public class AgentConfiguration

FILE: src/NetworkOptimizer.Storage/Models/ApLocation.cs
  class ApLocation (line 9) | public class ApLocation

FILE: src/NetworkOptimizer.Storage/Models/AuditResult.cs
  class AuditResult (line 9) | public class AuditResult

FILE: src/NetworkOptimizer.Storage/Models/Building.cs
  class Building (line 8) | public class Building

FILE: src/NetworkOptimizer.Storage/Models/ClientSignalLog.cs
  class ClientSignalLog (line 9) | public class ClientSignalLog

FILE: src/NetworkOptimizer.Storage/Models/DeviceSshConfiguration.cs
  class DeviceSshConfiguration (line 11) | public class DeviceSshConfiguration

FILE: src/NetworkOptimizer.Storage/Models/DismissedIssue.cs
  class DismissedIssue (line 8) | public class DismissedIssue

FILE: src/NetworkOptimizer.Storage/Models/ExternalSpeedTestServer.cs
  class ExternalSpeedTestServer (line 12) | public class ExternalSpeedTestServer
    method GenerateServerId (line 53) | public static string GenerateServerId(string name)

FILE: src/NetworkOptimizer.Storage/Models/FloorPlan.cs
  class FloorPlan (line 8) | public class FloorPlan

FILE: src/NetworkOptimizer.Storage/Models/FloorPlanImage.cs
  class FloorPlanImage (line 9) | public class FloorPlanImage

FILE: src/NetworkOptimizer.Storage/Models/GatewaySshSettings.cs
  class GatewaySshSettings (line 10) | public class GatewaySshSettings

FILE: src/NetworkOptimizer.Storage/Models/Iperf3Result.cs
  type SpeedTestDirection (line 12) | public enum SpeedTestDirection
  class Iperf3Result (line 42) | public class Iperf3Result
    method IsLocalLanClient (line 247) | public bool IsLocalLanClient(
Copy disabled (too large) Download .json
Condensed preview — 900 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (17,331K chars).
[
  {
    "path": ".dockerignore",
    "chars": 464,
    "preview": "# Git\n.git\n.gitignore\n.gitattributes\n\n# IDE\n.vs\n.vscode\n.idea\n*.user\n*.suo\n\n# Build outputs\n**/bin\n**/obj\n**/out\n\n# Test"
  },
  {
    "path": ".editorconfig",
    "chars": 1458,
    "preview": "# EditorConfig for NetworkOptimizer\n# https://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nen"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 35,
    "preview": "github: tvancott42\nko_fi: tjtuna42\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1667,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build-and-test:\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 5284,
    "preview": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ozark-connect/network-optimize"
  },
  {
    "path": ".gitignore",
    "chars": 703,
    "preview": "# Build outputs\nbin/\nobj/\nout/\npublish/\n\n# Go build artifacts\nsrc/uwnspeedtest/uwnspeedtest*\nsrc/cfspeedtest/cfspeedtest"
  },
  {
    "path": "Directory.Build.props",
    "chars": 278,
    "preview": "<Project>\n  <ItemGroup>\n    <PackageReference Include=\"MinVer\" Version=\"6.*\" PrivateAssets=\"all\" />\n  </ItemGroup>\n  <Pr"
  },
  {
    "path": "LICENSE",
    "chars": 3290,
    "preview": "License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.\n\"Business Source License\" is a trademark of"
  },
  {
    "path": "NetworkOptimizer.sln",
    "chars": 32215,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.3190"
  },
  {
    "path": "README.md",
    "chars": 21628,
    "preview": "<p align=\"center\">\n  <img src=\"docs/images/app-logo-v2.png\" alt=\"Network Optimizer\" width=\"200\">\n</p>\n\n# Network Optimiz"
  },
  {
    "path": "TODO.md",
    "chars": 29733,
    "preview": "# Network Optimizer - TODO / Future Enhancements\n\n## LAN Speed Test\n\n### Path Analysis Enhancements\n- ✅ ~~Direction-awar"
  },
  {
    "path": "docker/.dockerignore",
    "chars": 657,
    "preview": "# Git\n.git/\n.gitignore\n.gitattributes\n\n# Docker\ndocker/\nDockerfile\ndocker-compose.yml\n.dockerignore\n\n# Documentation\n*.m"
  },
  {
    "path": "docker/.env.example",
    "chars": 4390,
    "preview": "# Network Optimizer Environment Configuration\n# Copy this file to .env and update with your values\n\n# ===== Network Bind"
  },
  {
    "path": "docker/DEPLOYMENT.md",
    "chars": 37182,
    "preview": "# Deployment Guide\n\nProduction deployment guide for Network Optimizer.\n\n## Deployment Options\n\n| Option | Best For | Gui"
  },
  {
    "path": "docker/Dockerfile",
    "chars": 6354,
    "preview": "# Multi-stage build for Network Optimizer\n# Stage 1: Build stage with full .NET SDK\nFROM mcr.microsoft.com/dotnet/sdk:10"
  },
  {
    "path": "docker/NATIVE-DEPLOYMENT.md",
    "chars": 16868,
    "preview": "# Native Deployment Guide\n\nRun Network Optimizer directly on the host without Docker for maximum network performance.\n\n#"
  },
  {
    "path": "docker/QUICK-REFERENCE.md",
    "chars": 4834,
    "preview": "# Network Optimizer - Quick Reference Card\n\n## Quick Start\n\n### Option A: Pull Docker Image (Recommended)\n\n**Linux / Win"
  },
  {
    "path": "docker/README.md",
    "chars": 9770,
    "preview": "# Network Optimizer Docker Deployment\n\nComplete Docker infrastructure for the Ozark Connect Network Optimizer for UniFi."
  },
  {
    "path": "docker/docker-compose.local.yml",
    "chars": 5438,
    "preview": "# Local Development Configuration\n#\n# Usage:\n#   cd docker\n#   docker compose -f docker-compose.local.yml build\n#   dock"
  },
  {
    "path": "docker/docker-compose.macos.yml",
    "chars": 5184,
    "preview": "# macOS Development/Testing Configuration\n#\n# Usage:\n#   cd docker\n#   docker compose -f docker-compose.macos.yml build\n"
  },
  {
    "path": "docker/docker-compose.prod.yml",
    "chars": 3940,
    "preview": "services:\n  network-optimizer:\n    image: ghcr.io/ozark-connect/network-optimizer:latest\n    container_name: network-opt"
  },
  {
    "path": "docker/docker-compose.yml",
    "chars": 4092,
    "preview": "services:\n  network-optimizer:\n    build:\n      context: ..\n      dockerfile: docker/Dockerfile\n    image: ghcr.io/ozark"
  },
  {
    "path": "docker/entrypoint.sh",
    "chars": 976,
    "preview": "#!/bin/bash\nset -e\n\n# Set container timezone from TZ env var so .NET TimeZoneInfo.Local is correct\nif [ -n \"$TZ\" ] && [ "
  },
  {
    "path": "docker/grafana/dashboards/network-overview.json",
    "chars": 17208,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n  "
  },
  {
    "path": "docker/grafana/dashboards/security-posture.json",
    "chars": 30989,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n  "
  },
  {
    "path": "docker/grafana/dashboards/sqm-performance.json",
    "chars": 27928,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n  "
  },
  {
    "path": "docker/grafana/dashboards/switch-deep-dive.json",
    "chars": 28503,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n  "
  },
  {
    "path": "docker/grafana/provisioning/dashboards/dashboards.yml",
    "chars": 301,
    "preview": "apiVersion: 1\n\nproviders:\n  - name: 'Network Optimizer Dashboards'\n    orgId: 1\n    folder: 'Network Optimizer'\n    type"
  },
  {
    "path": "docker/grafana/provisioning/datasources/influxdb.yml",
    "chars": 358,
    "preview": "apiVersion: 1\n\ndatasources:\n  - name: InfluxDB-NetworkOptimizer\n    type: influxdb\n    access: proxy\n    url: http://inf"
  },
  {
    "path": "docker/openspeedtest/Dockerfile",
    "chars": 613,
    "preview": "# Ozark Connect Speed Test - based on OpenSpeedTest\n# Built from local customized source instead of upstream image\n# Pin"
  },
  {
    "path": "docker/openspeedtest/entrypoint.sh",
    "chars": 5139,
    "preview": "#!/bin/sh\n# Ozark Connect Speed Test - Entrypoint\n# Injects runtime configuration into config.js\n\n# Report TCP congestio"
  },
  {
    "path": "docker/openspeedtest/nginx.conf",
    "chars": 3374,
    "preview": "server {\n    server_name _;\n    listen 3000 reuseport;\n    listen [::]:3000 reuseport;\n\n    root /usr/share/nginx/html;\n"
  },
  {
    "path": "docs/MACOS-INSTALLATION.md",
    "chars": 5439,
    "preview": "# macOS Native Installation\n\nInstall Network Optimizer natively on macOS for maximum performance. Native installation is"
  },
  {
    "path": "docs/PLAN-unifi-api-abstraction.md",
    "chars": 6423,
    "preview": "# Plan: UniFi API Abstraction Layer\n\n## Background\n\nCurrently, the Network Optimizer \"hijacks\" the UniFi Controller's in"
  },
  {
    "path": "docs/features/speed-test-roadmap.md",
    "chars": 2520,
    "preview": "# Speed Test & Network Trace - Future Enhancements\n\n## Current State (Jan 2026)\n- Network path visualization with device"
  },
  {
    "path": "nuget.config",
    "chars": 224,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <packageSources>\n    <add key=\"nuget.org\" value=\"https://api.nu"
  },
  {
    "path": "renovate.json",
    "chars": 1591,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\",\n    \":semanticComm"
  },
  {
    "path": "scripts/README.md",
    "chars": 2300,
    "preview": "# Development Scripts\n\nBash scripts for common development tasks. Works with Git Bash on Windows or native bash on macOS"
  },
  {
    "path": "scripts/build-installer.ps1",
    "chars": 5682,
    "preview": "# Build Network Optimizer Windows Installer\n# Creates a self-contained MSI package\n\nparam(\n    [string]$Configuration = "
  },
  {
    "path": "scripts/build.sh",
    "chars": 269,
    "preview": "#!/bin/bash\n# Build the project\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$(dirna"
  },
  {
    "path": "scripts/clean.sh",
    "chars": 489,
    "preview": "#!/bin/bash\n# Clean build artifacts and coverage\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJ"
  },
  {
    "path": "scripts/coverage.runsettings",
    "chars": 592,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<RunSettings>\n  <DataCollectionRunSettings>\n    <DataCollectors>\n      <DataColl"
  },
  {
    "path": "scripts/coverage.sh",
    "chars": 1951,
    "preview": "#!/bin/bash\n# Run tests with code coverage and generate HTML report\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0"
  },
  {
    "path": "scripts/deploy-external-speedtest.sh",
    "chars": 6932,
    "preview": "#!/bin/bash\n# Deploy or update an external OpenSpeedTest server for WAN speed testing\n# This fetches only the speedtest "
  },
  {
    "path": "scripts/docker-build.sh",
    "chars": 366,
    "preview": "#!/bin/bash\n# Build Docker image locally\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT"
  },
  {
    "path": "scripts/docker-run.sh",
    "chars": 407,
    "preview": "#!/bin/bash\n# Run Docker container locally (macOS compatible)\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" "
  },
  {
    "path": "scripts/docker-stop.sh",
    "chars": 276,
    "preview": "#!/bin/bash\n# Stop Docker container\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$(d"
  },
  {
    "path": "scripts/extract-elevation0-from-images.py",
    "chars": 55428,
    "preview": "\"\"\"\nExtract Elevation 0 deg antenna pattern data from Ubiquiti reference images.\n\nUses Elevation 90 deg plots (column 2)"
  },
  {
    "path": "scripts/install-macos-native.sh",
    "chars": 22794,
    "preview": "#!/bin/bash\n# Install Network Optimizer natively on macOS\n# Usage: ./scripts/install-macos-native.sh\n#\n# This script:\n# "
  },
  {
    "path": "scripts/parse-antenna-patterns.ps1",
    "chars": 7024,
    "preview": "<#\n.SYNOPSIS\n    Parses Ubiquiti .ant antenna pattern files from zip archives into a single JSON file.\n\n.DESCRIPTION\n   "
  },
  {
    "path": "scripts/proxmox/README.md",
    "chars": 12121,
    "preview": "# Proxmox LXC Installation\n\nInstall Network Optimizer for UniFi in a Proxmox LXC container with a single command.\n\n## Qu"
  },
  {
    "path": "scripts/proxmox/install.sh",
    "chars": 38790,
    "preview": "#!/usr/bin/env bash\n\n# Network Optimizer for UniFi - Proxmox LXC Installation Script\n# https://github.com/Ozark-Connect/"
  },
  {
    "path": "scripts/publish.sh",
    "chars": 394,
    "preview": "#!/bin/bash\n# Publish for production\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$("
  },
  {
    "path": "scripts/reset-password.ps1",
    "chars": 9257,
    "preview": "<#\n.SYNOPSIS\n    Resets the Network Optimizer admin password on Windows.\n\n.DESCRIPTION\n    Stops the NetworkOptimizer se"
  },
  {
    "path": "scripts/reset-password.sh",
    "chars": 17920,
    "preview": "#!/usr/bin/env bash\n\n# Network Optimizer - Password Reset Script\n# https://github.com/Ozark-Connect/NetworkOptimizer\n#\n#"
  },
  {
    "path": "scripts/sync-perf-tweaks.ps1",
    "chars": 1346,
    "preview": "# Syncs performance tweak scripts from the unifi-perf-tweaks source repo\n# into the NetworkOptimizer embedded resources "
  },
  {
    "path": "scripts/test.sh",
    "chars": 243,
    "preview": "#!/bin/bash\n# Run all tests\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$(dirname \""
  },
  {
    "path": "scripts/watch.sh",
    "chars": 299,
    "preview": "#!/bin/bash\n# Run the web app with hot reload\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT"
  },
  {
    "path": "src/NetworkOptimizer.Agents/.gitignore",
    "chars": 745,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User"
  },
  {
    "path": "src/NetworkOptimizer.Agents/AgentDeployer.cs",
    "chars": 19360,
    "preview": "using System.Diagnostics;\nusing System.Text;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Agents.Models;\nu"
  },
  {
    "path": "src/NetworkOptimizer.Agents/AgentHealthMonitor.cs",
    "chars": 10994,
    "preview": "using System.Data;\nusing Microsoft.Data.Sqlite;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Agents.Models"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Models/AgentConfiguration.cs",
    "chars": 2065,
    "preview": "namespace NetworkOptimizer.Agents.Models;\n\n/// <summary>\n/// Configuration for a deployed agent\n/// </summary>\npublic cl"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Models/DeploymentResult.cs",
    "chars": 3544,
    "preview": "namespace NetworkOptimizer.Agents.Models;\n\n/// <summary>\n/// Result of an agent deployment operation\n/// </summary>\npubl"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Models/SshCredentials.cs",
    "chars": 1926,
    "preview": "namespace NetworkOptimizer.Agents.Models;\n\n/// <summary>\n/// SSH connection credentials supporting both password and key"
  },
  {
    "path": "src/NetworkOptimizer.Agents/NetworkOptimizer.Agents.csproj",
    "chars": 966,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <ImplicitUsings>"
  },
  {
    "path": "src/NetworkOptimizer.Agents/README.md",
    "chars": 2847,
    "preview": "# NetworkOptimizer.Agents\n\n> **Status: Future Project** - This library is planned but not yet implemented. The structure"
  },
  {
    "path": "src/NetworkOptimizer.Agents/ScriptRenderer.cs",
    "chars": 6055,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Agents.Models;\nusing Scriban;\nusing Scriban.Runtime;\n\nnamespa"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Templates/install-linux.sh.template",
    "chars": 4636,
    "preview": "#!/bin/bash\n#\n# Network Optimizer Agent - Linux Installation Script\n#\n\nset -e\n\necho \"=== Network Optimizer Agent Install"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Templates/linux-agent.service.template",
    "chars": 557,
    "preview": "[Unit]\nDescription=Network Optimizer Agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simpl"
  },
  {
    "path": "src/NetworkOptimizer.Agents/Templates/linux-agent.sh.template",
    "chars": 6562,
    "preview": "#!/bin/bash\n#\n# Network Optimizer - Linux Agent\n#\n# Configuration\nAGENT_ID=\"{{ agent_id }}\"\nDEVICE_NAME=\"{{ device_name "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/AlertCooldownTracker.cs",
    "chars": 1242,
    "preview": "using System.Collections.Concurrent;\n\nnamespace NetworkOptimizer.Alerts;\n\n/// <summary>\n/// In-memory cooldown tracker. "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/AlertCorrelationService.cs",
    "chars": 4285,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Alerts.Events;\nusing NetworkOptimizer.Alerts.Interfaces;\nusin"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/AlertProcessingService.cs",
    "chars": 10642,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/AlertRuleEvaluator.cs",
    "chars": 4745,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Alerts.Events;\nusing NetworkOptimizer.Alerts.Models;\n\nnamespa"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/DefaultAlertRules.cs",
    "chars": 5743,
    "preview": "using NetworkOptimizer.Alerts.Models;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts;\n\n/// <summa"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/DiscordChannelConfig.cs",
    "chars": 142,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class DiscordChannelConfig\n{\n    public string WebhookUrl { get; set"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/DiscordDeliveryChannel.cs",
    "chars": 7205,
    "preview": "using System.Text;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Alerts.Events;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/EmailChannelConfig.cs",
    "chars": 615,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class EmailChannelConfig\n{\n    public string SmtpHost { get; set; } "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/EmailDeliveryChannel.cs",
    "chars": 10993,
    "preview": "using System.Reflection;\nusing System.Text.Json;\nusing MailKit.Net.Smtp;\nusing MailKit.Security;\nusing Microsoft.Extensi"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/IAlertDeliveryChannel.cs",
    "chars": 1977,
    "preview": "using NetworkOptimizer.Alerts.Events;\nusing NetworkOptimizer.Alerts.Models;\nusing NetworkOptimizer.Core.Enums;\n\nnamespac"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/ISecretDecryptor.cs",
    "chars": 345,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\n/// <summary>\n/// Abstraction for decrypting secrets stored in delivery cha"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/NtfyChannelConfig.cs",
    "chars": 399,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class NtfyChannelConfig\n{\n    public string ServerUrl { get; set; } "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/NtfyDeliveryChannel.cs",
    "chars": 8518,
    "preview": "using System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing Netw"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/SlackChannelConfig.cs",
    "chars": 140,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class SlackChannelConfig\n{\n    public string WebhookUrl { get; set; "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/SlackDeliveryChannel.cs",
    "chars": 7868,
    "preview": "using System.Text;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Alerts.Events;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/TeamsChannelConfig.cs",
    "chars": 140,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class TeamsChannelConfig\n{\n    public string WebhookUrl { get; set; "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/TeamsDeliveryChannel.cs",
    "chars": 7804,
    "preview": "using System.Text;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Alerts.Events;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/TimestampFormatter.cs",
    "chars": 1637,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\n/// <summary>\n/// Formats UTC timestamps in the server's local timezone for"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/WebhookChannelConfig.cs",
    "chars": 386,
    "preview": "namespace NetworkOptimizer.Alerts.Delivery;\n\npublic class WebhookChannelConfig\n{\n    public string Url { get; set; } = s"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Delivery/WebhookDeliveryChannel.cs",
    "chars": 7890,
    "preview": "using System.Net.Http.Headers;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.Json;\nusing Micr"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/DigestService.cs",
    "chars": 11322,
    "preview": "using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\n"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Events/AlertEvent.cs",
    "chars": 2430,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Events;\n\n/// <summary>\n/// An event published by a"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Events/AlertEventBus.cs",
    "chars": 1282,
    "preview": "using System.Threading.Channels;\n\nnamespace NetworkOptimizer.Alerts.Events;\n\n/// <summary>\n/// Channel-based in-process "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Events/IAlertEventBus.cs",
    "chars": 608,
    "preview": "namespace NetworkOptimizer.Alerts.Events;\n\n/// <summary>\n/// In-process event bus for alert events. Publishers push even"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Interfaces/IAlertRepository.cs",
    "chars": 2984,
    "preview": "using NetworkOptimizer.Alerts.Models;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Interfaces;\n"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Interfaces/IDigestStateStore.cs",
    "chars": 394,
    "preview": "namespace NetworkOptimizer.Alerts.Interfaces;\n\n/// <summary>\n/// Persists digest \"last sent\" timestamps so they survive "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Interfaces/IScheduleRepository.cs",
    "chars": 1015,
    "preview": "using NetworkOptimizer.Alerts.Models;\n\nnamespace NetworkOptimizer.Alerts.Interfaces;\n\n/// <summary>\n/// Repository for s"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Models/AlertHistoryEntry.cs",
    "chars": 2762,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Models;\n\n/// <summary>\n/// Persisted record of an "
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Models/AlertIncident.cs",
    "chars": 1457,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Models;\n\n/// <summary>\n/// A correlated group of r"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Models/AlertRule.cs",
    "chars": 2466,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Models;\n\n/// <summary>\n/// User-configured rule th"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Models/DeliveryChannel.cs",
    "chars": 1728,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Alerts.Models;\n\n/// <summary>\n/// Delivery channel types."
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Models/ScheduledTask.cs",
    "chars": 2599,
    "preview": "namespace NetworkOptimizer.Alerts.Models;\n\n/// <summary>\n/// A scheduled task that runs periodically (audit, WAN speed t"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/NetworkOptimizer.Alerts.csproj",
    "chars": 948,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <ImplicitUsings>"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/ScheduleService.cs",
    "chars": 14818,
    "preview": "using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\n"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Templates/alert-email.html",
    "chars": 3688,
    "preview": "<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /></head>\n<body style=\"margin:0;padding:0;background:#1a2029;font-fam"
  },
  {
    "path": "src/NetworkOptimizer.Alerts/Templates/digest-email.html",
    "chars": 4073,
    "preview": "<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /></head>\n<body style=\"margin:0;padding:0;background:#1a2029;font-fam"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/AuditScorer.cs",
    "chars": 12543,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Audit.Scoring;\n\nnamespac"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/FirewallGroupHelper.cs",
    "chars": 8883,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptimizer.Audit.Analyzers;\n\n/"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/FirewallRuleAnalyzer.cs",
    "chars": 110597,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer."
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/FirewallRuleEvaluator.cs",
    "chars": 8137,
    "preview": "using NetworkOptimizer.Audit.Models;\n\nnamespace NetworkOptimizer.Audit.Analyzers;\n\n/// <summary>\n/// Utility for evaluat"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/FirewallRuleOverlapDetector.cs",
    "chars": 35888,
    "preview": "using System.Net;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Helpers;\nusing NetworkOptimizer.UniFi"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/FirewallRuleParser.cs",
    "chars": 37579,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer."
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/HttpAppIds.cs",
    "chars": 1634,
    "preview": "namespace NetworkOptimizer.Audit.Analyzers;\n\n/// <summary>\n/// Static lookup for HTTP-related application IDs used in Un"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/PortSecurityAnalyzer.cs",
    "chars": 51478,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer."
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/UpnpSecurityAnalyzer.cs",
    "chars": 20036,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.UniFi.Models;\n\nnamespace"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Analyzers/VlanAnalyzer.cs",
    "chars": 54394,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer."
  },
  {
    "path": "src/NetworkOptimizer.Audit/CHANGELOG.md",
    "chars": 6575,
    "preview": "# Changelog\n\nAll notable changes to NetworkOptimizer.Audit will be documented in this file.\n\n## [0.5.0] - 2026-01-03\n\n##"
  },
  {
    "path": "src/NetworkOptimizer.Audit/ConfigAuditEngine.cs",
    "chars": 71504,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Analyzers;\nusing NetworkOptimiz"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Constants/DetectionConstants.cs",
    "chars": 1529,
    "preview": "namespace NetworkOptimizer.Audit.Constants;\n\n/// <summary>\n/// Constants used for device type detection confidence scori"
  },
  {
    "path": "src/NetworkOptimizer.Audit/DeviceNameHints.cs",
    "chars": 2484,
    "preview": "using System.Text.RegularExpressions;\n\nnamespace NetworkOptimizer.Audit;\n\n/// <summary>\n/// Centralized device name patt"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/DnatDnsAnalyzer.cs",
    "chars": 25238,
    "preview": "using System.Net;\nusing System.Text.Json;\nusing NetworkOptimizer.Audit.Analyzers;\nusing NetworkOptimizer.Audit.Models;\nu"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/DnsAppIds.cs",
    "chars": 1640,
    "preview": "namespace NetworkOptimizer.Audit.Dns;\n\n/// <summary>\n/// Static lookup for DNS-related application IDs used in UniFi fir"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/DnsSecurityAnalyzer.cs",
    "chars": 137928,
    "preview": "using System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Analyzers;\nusing NetworkOptimiz"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/DnsStampDecoder.cs",
    "chars": 7760,
    "preview": "using System.Text;\n\nnamespace NetworkOptimizer.Audit.Dns;\n\n/// <summary>\n/// Decodes DNS Stamps (SDNS format) used by se"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/DohProviderRegistry.cs",
    "chars": 11981,
    "preview": "using System.Net;\nusing System.Text.RegularExpressions;\n\nnamespace NetworkOptimizer.Audit.Dns;\n\n/// <summary>\n/// Regist"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Dns/ThirdPartyDnsDetector.cs",
    "chars": 22506,
    "preview": "using System.Net;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing"
  },
  {
    "path": "src/NetworkOptimizer.Audit/IssueTypes.cs",
    "chars": 4961,
    "preview": "namespace NetworkOptimizer.Audit;\n\n/// <summary>\n/// Constants for all audit issue types. Use these instead of magic str"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/AuditIssue.cs",
    "chars": 3025,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Represents a single audit finding or issue\n/// </summary>\npu"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/AuditRequest.cs",
    "chars": 4745,
    "preview": "using System.Text.Json;\nusing NetworkOptimizer.Core.Models;\nusing NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptim"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/AuditResult.cs",
    "chars": 10953,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Comprehensive audit results for a UniFi network configuratio"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/AuditSeverity.cs",
    "chars": 503,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Severity levels for audit findings\n/// </summary>\npublic enu"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/DeviceAllowanceSettings.cs",
    "chars": 4144,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Settings for allowing certain device types on main/corporate"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/DeviceDetectionResult.cs",
    "chars": 2213,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Source of the device typ"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/FirewallAction.cs",
    "chars": 1378,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Firewall rule action types\n/// </summary>\npublic enum Firewa"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/FirewallRule.cs",
    "chars": 12256,
    "preview": "using NetworkOptimizer.Core.Helpers;\n\nnamespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Represents a firewall "
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/NetworkInfo.cs",
    "chars": 5600,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Network classification types based on purpose.\n/// Note: Cor"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/OfflineClientInfo.cs",
    "chars": 3278,
    "preview": "using NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Wrapper for an offline"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/PortInfo.cs",
    "chars": 5307,
    "preview": "using NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Represents a switch po"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/SwitchInfo.cs",
    "chars": 3068,
    "preview": "namespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Represents a UniFi switch or gateway device with switch port"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Models/WirelessClientInfo.cs",
    "chars": 3179,
    "preview": "using NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptimizer.Audit.Models;\n\n/// <summary>\n/// Wrapper for a wireless"
  },
  {
    "path": "src/NetworkOptimizer.Audit/NetworkOptimizer.Audit.csproj",
    "chars": 885,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <ImplicitUsings>"
  },
  {
    "path": "src/NetworkOptimizer.Audit/README.md",
    "chars": 10813,
    "preview": "# NetworkOptimizer.Audit\n\nComprehensive security audit engine for UniFi network configurations. Analyzes switch ports, V"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/AccessPortVlanRule.cs",
    "chars": 13188,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nusing Audit"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/CameraVlanRule.cs",
    "chars": 11763,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\nusing NetworkOptimizer.Core.Models;\n\nusing Audit"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/FirewallAnyAnyRule.cs",
    "chars": 2227,
    "preview": "using NetworkOptimizer.Audit.Models;\n\nnamespace NetworkOptimizer.Audit.Rules;\n\n/// <summary>\n/// Detects firewall rules "
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/IAuditRule.cs",
    "chars": 13315,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Audit.Services;\nusing NetworkOptimizer.Core.Enums;\nusing Net"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/IWirelessAuditRule.cs",
    "chars": 4183,
    "preview": "using NetworkOptimizer.Audit.Models;\n\nusing AuditSeverity = NetworkOptimizer.Audit.Models.AuditSeverity;\n\nnamespace Netw"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/IotVlanRule.cs",
    "chars": 8199,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nusing AuditSeverity = NetworkOptimizer.Audit.Mo"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/MacRestrictionRule.cs",
    "chars": 6229,
    "preview": "using NetworkOptimizer.Audit.Models;\n\nnamespace NetworkOptimizer.Audit.Rules;\n\n/// <summary>\n/// Detects access ports wi"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/PortIsolationRule.cs",
    "chars": 2489,
    "preview": "using NetworkOptimizer.Audit.Models;\n\nnamespace NetworkOptimizer.Audit.Rules;\n\n/// <summary>\n/// Checks if security-sens"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/PortNameHelper.cs",
    "chars": 1481,
    "preview": "using System.Text.RegularExpressions;\n\nnamespace NetworkOptimizer.Audit.Rules;\n\n/// <summary>\n/// Shared helper for dete"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/UnusedPortRule.cs",
    "chars": 5102,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\n\nnamespace NetworkOptimizer.Audit.Rules;\n\n/// <"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/VlanPlacementChecker.cs",
    "chars": 17674,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Audit.Scoring;\nusing NetworkOptimizer.Core.Enums;\n\nusing Aud"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/VlanSubnetMismatchRule.cs",
    "chars": 5306,
    "preview": "using System.Net;\nusing System.Net.Sockets;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Helpers;\n\nu"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/WiredSubnetMismatchRule.cs",
    "chars": 5919,
    "preview": "using System.Net;\nusing System.Net.Sockets;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Helpers;\n\nu"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/WirelessCameraVlanRule.cs",
    "chars": 2553,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nusing AuditSeverity = NetworkOptimizer.Audit.Mo"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Rules/WirelessIotVlanRule.cs",
    "chars": 2544,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nusing AuditSeverity = NetworkOptimizer.Audit.Mo"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Scoring/ScoreConstants.cs",
    "chars": 2374,
    "preview": "namespace NetworkOptimizer.Audit.Scoring;\n\n/// <summary>\n/// Constants for security score calculation.\n/// Centralizes a"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/Detectors/FingerprintDetector.cs",
    "chars": 39813,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\nusing Networ"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/Detectors/MacOuiDetector.cs",
    "chars": 16723,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Audit.Services.Detec"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/Detectors/NamePatternDetector.cs",
    "chars": 10141,
    "preview": "using NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Audit.Services.Detec"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/DeviceTypeDetectionService.cs",
    "chars": 68618,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Models;\nusing NetworkOptimizer.Audit.Services.Detectors"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/FirewallZoneLookup.cs",
    "chars": 7440,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.UniFi.Models;\n\nnamespace NetworkOptimizer.Audit.Services;\n\n//"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/IIeeeOuiDatabase.cs",
    "chars": 1299,
    "preview": "namespace NetworkOptimizer.Audit.Services;\n\n/// <summary>\n/// Downloads and indexes the IEEE OUI database for MAC vendor"
  },
  {
    "path": "src/NetworkOptimizer.Audit/Services/IeeeOuiDatabase.cs",
    "chars": 7488,
    "preview": "using System.Collections.Concurrent;\nusing Microsoft.Extensions.Logging;\n\nnamespace NetworkOptimizer.Audit.Services;\n\n//"
  },
  {
    "path": "src/NetworkOptimizer.Core/Caching/AsyncCachedValue.cs",
    "chars": 1301,
    "preview": "namespace NetworkOptimizer.Core.Caching;\n\n/// <summary>\n/// Thread-safe async cached value with expiration support.\n/// "
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/AgentType.cs",
    "chars": 1924,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Represents the type of monitoring agent deployed on network se"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/AlertSeverity.cs",
    "chars": 228,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Alert severity levels for monitoring and notification alerts.\n"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/AlertStatus.cs",
    "chars": 184,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Alert lifecycle status.\n/// </summary>\npublic enum AlertStatus"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/AuditSeverity.cs",
    "chars": 1861,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Represents the severity level of an audit finding.\n/// </summa"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/ClientDeviceCategory.cs",
    "chars": 11634,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Categories for network client devices, used for security audit"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/DeviceType.cs",
    "chars": 9521,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Unified device type enum for all network devices.\n/// Stored a"
  },
  {
    "path": "src/NetworkOptimizer.Core/Enums/MeasurementType.cs",
    "chars": 4547,
    "preview": "namespace NetworkOptimizer.Core.Enums;\n\n/// <summary>\n/// Represents the types of measurements that can be collected and"
  },
  {
    "path": "src/NetworkOptimizer.Core/Extensions/ServiceProviderExtensions.cs",
    "chars": 1725,
    "preview": "using Microsoft.Extensions.DependencyInjection;\n\nnamespace NetworkOptimizer.Core.Extensions;\n\n/// <summary>\n/// Extensio"
  },
  {
    "path": "src/NetworkOptimizer.Core/FeatureFlags.cs",
    "chars": 433,
    "preview": "namespace NetworkOptimizer.Core;\n\n/// <summary>\n/// Simple static feature flags for toggling features at compile/deploy "
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/CloudflareIpRanges.cs",
    "chars": 4764,
    "preview": "using System.Net;\n\nnamespace NetworkOptimizer.Core.Helpers;\n\n/// <summary>\n/// Well-known Cloudflare IP ranges for detec"
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/DisplayFormatters.cs",
    "chars": 20582,
    "preview": "namespace NetworkOptimizer.Core.Helpers;\n\n/// <summary>\n/// Shared display formatting utilities used by both web UI and "
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/JsonExtensions.cs",
    "chars": 5825,
    "preview": "using System.Text.Json;\n\nnamespace NetworkOptimizer.Core.Helpers;\n\n/// <summary>\n/// Extension methods for System.Text.J"
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/NetworkFormatHelpers.cs",
    "chars": 1217,
    "preview": "namespace NetworkOptimizer.Core.Helpers;\n\n/// <summary>\n/// Utility methods for formatting network-related strings\n/// <"
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/NetworkUtilities.cs",
    "chars": 23129,
    "preview": "using System.Net;\nusing System.Net.NetworkInformation;\nusing System.Net.Sockets;\n\nnamespace NetworkOptimizer.Core.Helper"
  },
  {
    "path": "src/NetworkOptimizer.Core/Helpers/ProcessUtilities.cs",
    "chars": 857,
    "preview": "namespace NetworkOptimizer.Core.Helpers;\n\n/// <summary>\n/// Utilities for locating and running external processes.\n/// <"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/IAgentDeployer.cs",
    "chars": 10141,
    "preview": "using NetworkOptimizer.Core.Enums;\nusing NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n///"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/IAuditEngine.cs",
    "chars": 5766,
    "preview": "using NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n/// <summary>\n/// Interface for the ne"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/IMetricsStorage.cs",
    "chars": 7827,
    "preview": "using NetworkOptimizer.Core.Enums;\nusing NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n///"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/IReportGenerator.cs",
    "chars": 16587,
    "preview": "using NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n/// <summary>\n/// Interface for genera"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/ISqmManager.cs",
    "chars": 10385,
    "preview": "using NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n/// <summary>\n/// Interface for managi"
  },
  {
    "path": "src/NetworkOptimizer.Core/Interfaces/IUniFiApiClient.cs",
    "chars": 5718,
    "preview": "using NetworkOptimizer.Core.Models;\n\nnamespace NetworkOptimizer.Core.Interfaces;\n\n/// <summary>\n/// Interface for intera"
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/AgentStatus.cs",
    "chars": 7290,
    "preview": "using System.Net;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represe"
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/AuditResult.cs",
    "chars": 6103,
    "preview": "using NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represents the result of "
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/NetworkConfiguration.cs",
    "chars": 11941,
    "preview": "using System.Net;\n\nnamespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represents the complete network configurat"
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/ProtectCamera.cs",
    "chars": 5639,
    "preview": "namespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represents a UniFi Protect camera/device with MAC and name\n//"
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/SqmConfiguration.cs",
    "chars": 7194,
    "preview": "namespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represents Smart Queue Management (SQM) configuration for tra"
  },
  {
    "path": "src/NetworkOptimizer.Core/Models/UniFiDevice.cs",
    "chars": 3394,
    "preview": "using System.Net;\nusing NetworkOptimizer.Core.Enums;\n\nnamespace NetworkOptimizer.Core.Models;\n\n/// <summary>\n/// Represe"
  },
  {
    "path": "src/NetworkOptimizer.Core/NetworkOptimizer.Core.csproj",
    "chars": 329,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <ImplicitUsings>"
  },
  {
    "path": "src/NetworkOptimizer.Core/VendorSpecificAttribute.cs",
    "chars": 764,
    "preview": "namespace NetworkOptimizer.Core;\n\n/// <summary>\n/// Marks code that contains vendor-specific assumptions (raw JSON parsi"
  },
  {
    "path": "src/NetworkOptimizer.Diagnostics/Analyzers/ApLockAnalyzer.cs",
    "chars": 8285,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Services;\nusing NetworkOptimizer.Core.Enums;\nusing Netw"
  },
  {
    "path": "src/NetworkOptimizer.Diagnostics/Analyzers/PerformanceAnalyzer.cs",
    "chars": 44455,
    "preview": "using System.Net;\nusing System.Text.Json;\nusing Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Audit.Services;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Diagnostics/Analyzers/PortProfile8021xAnalyzer.cs",
    "chars": 6422,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Diagnostics.Models;\nusing NetworkOptimizer.UniFi.Helpers;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Diagnostics/Analyzers/PortProfileSuggestionAnalyzer.cs",
    "chars": 65846,
    "preview": "using Microsoft.Extensions.Logging;\nusing NetworkOptimizer.Diagnostics.Models;\nusing NetworkOptimizer.UniFi.Helpers;\nusi"
  },
  {
    "path": "src/NetworkOptimizer.Diagnostics/Analyzers/StreamingAppIds.cs",
    "chars": 3284,
    "preview": "namespace NetworkOptimizer.Diagnostics.Analyzers;\n\n/// <summary>\n/// UniFi application IDs for QoS rule matching.\n/// Th"
  }
]

// ... and 700 more files (download for full content)

About this extraction

This page contains the full source code of the Ozark-Connect/NetworkOptimizer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 900 files (45.7 MB), approximately 4.2M tokens, and a symbol index with 12936 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!