mirror of
https://github.com/freedbygrace/PSOPNSenseAPI.git
synced 2026-07-26 20:08:30 +00:00
Compare commits
4 Commits
v2025.04.15.1740
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8731b7303f | |||
| 5330e620b2 | |||
| 5c845b1be6 | |||
| a413875058 |
@@ -3,3 +3,4 @@ bin/
|
||||
obj/
|
||||
*.zip
|
||||
output/*.zip
|
||||
release/
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 PSOPNSenseAPI Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,214 @@
|
||||
@{
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'lib\PSOPNSenseAPI.dll'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '2025.04.15.1749'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = @('Desktop', 'Core')
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '9a3b4c55-5f9a-4b8c-87d9-9a7a5cdd5c9f'
|
||||
|
||||
# Author of this module
|
||||
Author = 'PSOPNSenseAPI Contributors'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'PSOPNSenseAPI'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) 2025 PSOPNSenseAPI Contributors. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'PowerShell module for interacting with the OPNSense API to configure firewalls'
|
||||
|
||||
# Minimum version of the PowerShell engine required by this module
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
# Name of the PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = '4.7.2'
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
ClrVersion = '4.0'
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @()
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @(
|
||||
'Apply-OPNSenseFirewallChanges',
|
||||
'Connect-OPNSense',
|
||||
'Connect-OPNSenseTailscale',
|
||||
'ConvertTo-OPNSenseNetworkNotation',
|
||||
'Disable-OPNSenseCronJob',
|
||||
'Disable-OPNSenseFirewallRule',
|
||||
'Disable-OPNSensePlugin',
|
||||
'Disable-OPNSenseTailscale',
|
||||
'Disconnect-OPNSense',
|
||||
'Disconnect-OPNSenseTailscale',
|
||||
'Enable-OPNSenseCronJob',
|
||||
'Enable-OPNSenseFirewallRule',
|
||||
'Enable-OPNSensePlugin',
|
||||
'Enable-OPNSenseTailscale',
|
||||
'Export-OPNSenseConfig',
|
||||
'Get-OPNSenseAlias',
|
||||
'Get-OPNSenseConfig',
|
||||
'Get-OPNSenseConfigBackup',
|
||||
'Get-OPNSenseConnection',
|
||||
'Get-OPNSenseCronJob',
|
||||
'Get-OPNSenseDHCPLease',
|
||||
'Get-OPNSenseDHCPOption',
|
||||
'Get-OPNSenseDHCPServer',
|
||||
'Get-OPNSenseDHCPStaticMapping',
|
||||
'Get-OPNSenseDNSForwarding',
|
||||
'Get-OPNSenseDNSForwardingHost',
|
||||
'Get-OPNSenseDNSOverride',
|
||||
'Get-OPNSenseDNSServer',
|
||||
'Get-OPNSenseFirewallRule',
|
||||
'Get-OPNSenseFirmware',
|
||||
'Get-OPNSenseGateway',
|
||||
'Get-OPNSenseInterface',
|
||||
'Get-OPNSenseInterfaceStatistics',
|
||||
'Get-OPNSensePlugin',
|
||||
'Get-OPNSensePortForwardingRule',
|
||||
'Get-OPNSenseRoute',
|
||||
'Get-OPNSenseSystemDNS',
|
||||
'Get-OPNSenseTailscaleStatus',
|
||||
'Get-OPNSenseUser',
|
||||
'Get-OPNSenseVLAN',
|
||||
'Import-OPNSenseConfig',
|
||||
'Install-OPNSensePlugin',
|
||||
'Invoke-OPNSenseNetworkCalculation',
|
||||
'New-OPNSenseAlias',
|
||||
'New-OPNSenseCronJob',
|
||||
'New-OPNSenseDHCPOption',
|
||||
'New-OPNSenseDHCPStaticMapping',
|
||||
'New-OPNSenseDNSForwardingHost',
|
||||
'New-OPNSenseDNSOverride',
|
||||
'New-OPNSenseFirewallRule',
|
||||
'New-OPNSenseGateway',
|
||||
'New-OPNSensePortForwardingRule',
|
||||
'New-OPNSenseRoute',
|
||||
'New-OPNSenseSubnetVLANs',
|
||||
'New-OPNSenseUser',
|
||||
'New-OPNSenseVLAN',
|
||||
'Remove-OPNSenseAlias',
|
||||
'Remove-OPNSenseCronJob',
|
||||
'Remove-OPNSenseDHCPLease',
|
||||
'Remove-OPNSenseDHCPOption',
|
||||
'Remove-OPNSenseDHCPStaticMapping',
|
||||
'Remove-OPNSenseDNSForwardingHost',
|
||||
'Remove-OPNSenseDNSOverride',
|
||||
'Remove-OPNSenseFirewallRule',
|
||||
'Remove-OPNSenseGateway',
|
||||
'Remove-OPNSensePortForwardingRule',
|
||||
'Remove-OPNSenseRoute',
|
||||
'Remove-OPNSenseUser',
|
||||
'Remove-OPNSenseVLAN',
|
||||
'Restart-OPNSenseFirewall',
|
||||
'Restart-OPNSenseInterface',
|
||||
'Restore-OPNSenseConfig',
|
||||
'Set-OPNSenseAlias',
|
||||
'Set-OPNSenseCronJob',
|
||||
'Set-OPNSenseDHCPOption',
|
||||
'Set-OPNSenseDHCPServer',
|
||||
'Set-OPNSenseDHCPStaticMapping',
|
||||
'Set-OPNSenseDNSForwarding',
|
||||
'Set-OPNSenseDNSForwardingHost',
|
||||
'Set-OPNSenseDNSServer',
|
||||
'Set-OPNSenseFirewallRule',
|
||||
'Set-OPNSenseGateway',
|
||||
'Set-OPNSenseInterface',
|
||||
'Set-OPNSensePortForwardingRule',
|
||||
'Set-OPNSenseRoute',
|
||||
'Set-OPNSenseSystemDNS',
|
||||
'Set-OPNSenseUser',
|
||||
'Set-OPNSenseVLAN',
|
||||
'Uninstall-OPNSensePlugin',
|
||||
'Update-OPNSenseFirmware',
|
||||
'Start-OPNSenseFirmwareUpgrade',
|
||||
'Test-OPNSenseThreading'
|
||||
)
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = @()
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
Tags = @('PowerShell', 'OPNSense', 'Firewall', 'API')
|
||||
|
||||
# A URL to the license for this module.
|
||||
LicenseUri = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/LICENSE'
|
||||
|
||||
# A URL to the main website for this project.
|
||||
ProjectUri = 'https://github.com/freedbygrace/PSOPNSenseAPI'
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
ReleaseNotes = 'https://github.com/freedbygrace/PSOPNSenseAPI/blob/main/CHANGELOG.md'
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = $false
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = @()
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfoURI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
# PSOPNSenseAPI
|
||||
|
||||
PowerShell module for interacting with the OPNSense API to configure firewalls.
|
||||
|
||||
## Overview
|
||||
|
||||
PSOPNSenseAPI is a PowerShell module that provides cmdlets for managing OPNSense firewalls through their API. The module is built as a binary module in C# and is compatible with both PowerShell 5.1 and PowerShell 7.
|
||||
|
||||
## Features
|
||||
|
||||
- Connect to OPNSense firewalls using API credentials
|
||||
- Manage firewall rules (create, read, update, delete)
|
||||
- Configure NAT rules and port forwarding
|
||||
- Manage aliases (host, network, port, URL, etc.)
|
||||
- Manage network interfaces and VLANs
|
||||
- Configure DNS settings, overrides, and forwarding
|
||||
- Configure system DNS servers
|
||||
- Backup, restore, export, and import configurations
|
||||
- Manage plugins (install, uninstall, enable, disable)
|
||||
- Manage users and permissions
|
||||
- Update and upgrade firmware
|
||||
- Reboot firewall with wait for reconnection
|
||||
- Create VLANs from subnet divisions
|
||||
- Configure DHCP servers, static mappings, leases, and options
|
||||
- Manage cron jobs
|
||||
- Configure and manage Tailscale VPN (with auto-installation)
|
||||
- Manage gateways and static routes
|
||||
- Network calculation utilities (subnet, supernet, CIDR conversion)
|
||||
- Apply and revert configuration changes
|
||||
- Extensive logging and error handling
|
||||
- Compatible with both PowerShell 5.1 and PowerShell 7
|
||||
|
||||
## Requirements
|
||||
|
||||
- PowerShell 5.1 or PowerShell 7+
|
||||
- .NET Framework 4.7.2+ (for PowerShell 5.1)
|
||||
- .NET Core 3.1+ (for PowerShell 7)
|
||||
|
||||
## Installation
|
||||
|
||||
```powershell
|
||||
# Install from PowerShell Gallery (when published)
|
||||
Install-Module -Name PSOPNSenseAPI
|
||||
|
||||
# Or install manually
|
||||
# 1. Download the module
|
||||
# 2. Extract to a directory in your PSModulePath
|
||||
# 3. Import the module
|
||||
Import-Module PSOPNSenseAPI
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```powershell
|
||||
# Connect to an OPNSense firewall
|
||||
Connect-OPNSense -Server "https://firewall.example.com" -ApiKey "your_api_key" -ApiSecret "your_api_secret" -SkipCertificateCheck
|
||||
|
||||
# Get all firewall rules
|
||||
Get-OPNSenseFirewallRule
|
||||
|
||||
# Create a new firewall rule
|
||||
New-OPNSenseFirewallRule -Description "Allow HTTP" -Protocol TCP -SourceNet "192.168.1.0/24" -DestinationPort 80 -Action Pass
|
||||
|
||||
# Apply changes
|
||||
Apply-OPNSenseFirewallChanges
|
||||
|
||||
# Manage aliases
|
||||
Get-OPNSenseAlias
|
||||
New-OPNSenseAlias -Name "WebServers" -Type host -Content "192.168.1.10,192.168.1.11" -Description "Web Servers" -Apply
|
||||
New-OPNSenseAlias -Name "WebPorts" -Type port -Content "80,443" -Protocol TCP -Description "Web Ports" -Apply
|
||||
Set-OPNSenseAlias -Uuid "9e4ec4f0-9dd1-4fa3-8c1d-8a8e9d772b0f" -Content "192.168.1.10,192.168.1.11,192.168.1.12" -Apply
|
||||
|
||||
# Manage interfaces
|
||||
Get-OPNSenseInterface
|
||||
Set-OPNSenseInterface -Name "lan" -Description "Local Network" -IpAddress "192.168.1.1" -SubnetMask "24"
|
||||
|
||||
# Create VLANs from subnet divisions
|
||||
New-OPNSenseSubnetVLANs -ParentInterface "em0" -Network "192.168.0.0/24" -SubnetMaskBits 27 -StartingVlanId 10 -VlanIdIncrement 10 -EnableDHCP
|
||||
|
||||
# Configure DNS
|
||||
Set-OPNSenseDNSServer -Forwarding -Forwarders "8.8.8.8","8.8.4.4" -Apply
|
||||
New-OPNSenseDNSOverride -Hostname "server" -Domain "local" -IpAddress "192.168.1.10"
|
||||
|
||||
# Manage plugins
|
||||
Get-OPNSensePlugin -Installed
|
||||
Install-OPNSensePlugin -Name "os-acme-client" -Wait
|
||||
|
||||
# Manage users
|
||||
New-OPNSenseUser -Username "john" -Password "P@ssw0rd" -FullName "John Doe" -Groups "admins"
|
||||
|
||||
# Configure DHCP
|
||||
Get-OPNSenseDHCPServer
|
||||
Set-OPNSenseDHCPServer -Interface "lan" -RangeFrom "192.168.1.100" -RangeTo "192.168.1.200" -Apply
|
||||
New-OPNSenseDHCPStaticMapping -Interface "lan" -MacAddress "00:11:22:33:44:55" -IpAddress "192.168.1.50" -Hostname "printer"
|
||||
Get-OPNSenseDHCPLease
|
||||
Remove-OPNSenseDHCPLease -MacAddress "00:11:22:33:44:55" -Apply
|
||||
Get-OPNSenseDHCPOption -Interface "lan"
|
||||
New-OPNSenseDHCPOption -Interface "lan" -Number 66 -Value "192.168.1.10" -Description "TFTP Server" -Apply
|
||||
|
||||
# Manage cron jobs
|
||||
Get-OPNSenseCronJob
|
||||
New-OPNSenseCronJob -Description "Daily backup" -Command "/usr/local/bin/backup.sh" -Minutes "0" -Hours "2" -Apply
|
||||
|
||||
# Configure and manage Tailscale
|
||||
Get-OPNSenseTailscaleStatus -IncludeInterfaces
|
||||
Enable-OPNSenseTailscale -AcceptDns -AcceptRoutes -Force
|
||||
# Advertise subnet routes to Tailscale network
|
||||
Enable-OPNSenseTailscale -AdvertiseRoutes -SubnetRoutes "192.168.1.0/24","10.0.0.0/8" -Force
|
||||
Connect-OPNSenseTailscale -AuthKey "tskey-auth-abcdef123456" -InstallIfMissing -Force
|
||||
|
||||
# Manage DNS forwarding
|
||||
Get-OPNSenseDNSForwarding
|
||||
Set-OPNSenseDNSForwarding -Enabled -DnsServers "8.8.8.8","8.8.4.4" -Apply
|
||||
New-OPNSenseDNSForwardingHost -Domain "example.com" -Server "192.168.1.10" -Apply
|
||||
|
||||
# Configure system DNS
|
||||
Get-OPNSenseSystemDNS
|
||||
Set-OPNSenseSystemDNS -Hostname "firewall" -Domain "example.com" -DnsServers "1.1.1.1","1.0.0.1" -Apply
|
||||
|
||||
# Manage gateways and routes
|
||||
Get-OPNSenseGateway -IncludeStatus
|
||||
New-OPNSenseGateway -Name "WAN2_GW" -Interface "opt1" -IpAddress "203.0.113.1" -Description "Secondary WAN" -Apply
|
||||
Get-OPNSenseRoute
|
||||
New-OPNSenseRoute -Network "192.168.100.0/24" -Gateway "WAN2_GW" -Description "Remote Office" -Apply
|
||||
|
||||
# Network utilities
|
||||
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Info
|
||||
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/16" -Operation Subnet -PrefixLength 24
|
||||
ConvertTo-OPNSenseNetworkNotation -CIDR 24 # Returns "255.255.255.0"
|
||||
|
||||
# Advanced supernetting
|
||||
Invoke-OPNSenseNetworkCalculation -Network "192.168.1.0/24" -Operation Supernet -AdditionalNetworks "192.168.2.0/24"
|
||||
Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSummarize -AdditionalNetworks "10.0.1.0/24","10.0.2.0/24"
|
||||
|
||||
# Firmware management
|
||||
Update-OPNSenseFirmware -Wait
|
||||
|
||||
# Major firmware upgrade
|
||||
Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200
|
||||
|
||||
# Reboot firewall
|
||||
Restart-OPNSenseFirewall -Wait -Timeout 300
|
||||
|
||||
# Configuration management
|
||||
Get-OPNSenseConfig | Restore-OPNSenseConfig -Force # Get and restore configuration in one line
|
||||
$config = Get-OPNSenseConfig # Get configuration as XML document
|
||||
Export-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml")
|
||||
Import-OPNSenseConfig -Path (New-Object System.IO.FileInfo "C:\Backups\opnsense-config.xml") -Force
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation, see the [docs](./docs) directory or use PowerShell's built-in help:
|
||||
|
||||
```powershell
|
||||
Get-Help Connect-OPNSense -Full
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Build and Test
|
||||
|
||||
To build and test the module locally:
|
||||
|
||||
```powershell
|
||||
# Run tests
|
||||
.\build\test.ps1
|
||||
|
||||
# Build the module
|
||||
.\build\build.ps1
|
||||
|
||||
# Build a release version with automatic versioning
|
||||
.\build\build-release.ps1 -Clean -Test -Package
|
||||
```
|
||||
|
||||
### Versioning
|
||||
|
||||
The module uses a versioning scheme of `yyyy.MM.dd.HHmm` for releases, which is automatically generated during the build process.
|
||||
|
||||
### CI/CD
|
||||
|
||||
The project uses GitHub Actions for continuous integration and deployment:
|
||||
|
||||
- Builds and tests are run on every push to the main branch
|
||||
- Release packages are automatically created with the versioning scheme
|
||||
- Release artifacts are uploaded to GitHub Releases
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please follow these steps:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Make your changes
|
||||
4. Run tests to ensure they pass
|
||||
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
7. Open a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -22,7 +22,7 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
/// </example>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommunications.Connect, "OPNSense")]
|
||||
[OutputType(typeof(void))]
|
||||
[OutputType(typeof(PSObject))]
|
||||
public class ConnectOPNSenseCmdlet : OPNSenseBaseCmdlet
|
||||
{
|
||||
/// <summary>
|
||||
@@ -64,14 +64,22 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
protected override void ProcessRecordInternal()
|
||||
{
|
||||
// Check if already connected
|
||||
if (OPNSenseSession.IsConnected && !Force.IsPresent)
|
||||
var sessionState = OPNSenseSessionState.Instance;
|
||||
if (sessionState.ApiClient != null && sessionState.ApiClient.IsConnected && !Force.IsPresent)
|
||||
{
|
||||
WriteWarning($"Already connected to {OPNSenseSession.BaseUrl}. Use -Force to reconnect.");
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
|
||||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
|
||||
{
|
||||
WriteWarning($"Already connected to {sessionState.ApiClient.BaseUrl}. Use -Force to reconnect.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Dispose existing connection if there is one
|
||||
OPNSenseSession.Current?.Dispose();
|
||||
sessionState.ApiClient?.Dispose();
|
||||
|
||||
// Create a new logger
|
||||
var logger = new PowerShellLogger(this);
|
||||
@@ -80,9 +88,31 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
var client = new OPNSenseApiClient(Server, ApiKey, ApiSecret, SkipCertificateCheck.IsPresent, logger);
|
||||
|
||||
// Set the current session
|
||||
OPNSenseSession.Current = client;
|
||||
sessionState.ApiClient = client;
|
||||
OPNSenseSession.Current = client; // Keep this for backward compatibility
|
||||
|
||||
// Reset API endpoints
|
||||
sessionState.ResetApiEndpoints();
|
||||
|
||||
WriteVerbose($"Connected to OPNSense firewall at {Server}");
|
||||
|
||||
// Detect API version
|
||||
try
|
||||
{
|
||||
var apiVersion = sessionState.ApiEndpoints.GetOPNSenseVersion();
|
||||
WriteVerbose($"Detected OPNSense API version: {apiVersion}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteVerbose($"Failed to detect OPNSense API version: {ex.Message}. Using legacy API endpoints.");
|
||||
}
|
||||
|
||||
// Create and return detailed connection info object
|
||||
var connectionInfoHelper = new OPNSenseConnectionInfo(client, logger);
|
||||
var connectionInfo = connectionInfoHelper.GetConnectionInfo();
|
||||
|
||||
WriteObject(connectionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Management.Automation;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
using PSOPNSenseAPI.Services;
|
||||
|
||||
namespace PSOPNSenseAPI.Cmdlets
|
||||
{
|
||||
@@ -21,27 +22,46 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
/// <summary>
|
||||
/// Processes the cmdlet
|
||||
/// </summary>
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
// Override the base implementation to avoid checking for connection
|
||||
// since this cmdlet is used to check the connection status
|
||||
base.BeginProcessing();
|
||||
Logger = new PowerShellLogger(this);
|
||||
}
|
||||
|
||||
protected override void ProcessRecordInternal()
|
||||
{
|
||||
if (!OPNSenseSession.IsConnected)
|
||||
var sessionState = OPNSenseSessionState.Instance;
|
||||
// Create a logger
|
||||
var logger = new PowerShellLogger(this);
|
||||
|
||||
if (sessionState.ApiClient == null)
|
||||
{
|
||||
WriteWarning("Not connected to any OPNSense firewall.");
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
|
||||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
|
||||
{
|
||||
WriteWarning("Not connected to any OPNSense firewall.");
|
||||
}
|
||||
|
||||
// Return an empty connection info object
|
||||
var emptyConnectionInfo = new PSObject();
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Server", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Connected", false));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ApiVersion", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ProductName", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("ProductVersion", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("OsVersion", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Hostname", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("SystemTime", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("Uptime", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("CpuUsage", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("MemoryUsage", null));
|
||||
emptyConnectionInfo.Properties.Add(new PSNoteProperty("LastUpdate", null));
|
||||
|
||||
WriteObject(emptyConnectionInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
var connectionInfo = new PSObject();
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Server", OPNSenseSession.BaseUrl));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Connected", OPNSenseSession.IsConnected));
|
||||
// Create and return detailed connection info object
|
||||
var connectionInfoHelper = new OPNSenseConnectionInfo(sessionState.ApiClient, logger);
|
||||
var connectionInfo = connectionInfoHelper.GetConnectionInfo();
|
||||
|
||||
WriteObject(connectionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading.Tasks;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
using PSOPNSenseAPI.Services;
|
||||
@@ -10,6 +9,7 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
/// <summary>
|
||||
/// Base class for all OPNSense cmdlets
|
||||
/// </summary>
|
||||
[CmdletBinding()]
|
||||
public abstract class OPNSenseBaseCmdlet : PSCmdlet
|
||||
{
|
||||
/// <summary>
|
||||
@@ -18,9 +18,28 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
base.BeginProcessing();
|
||||
|
||||
// Set default WarningPreference to SilentlyContinue if not explicitly set
|
||||
if (!MyInvocation.BoundParameters.ContainsKey("WarningAction"))
|
||||
{
|
||||
// Store the original preference
|
||||
var originalWarningPreference = SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue);
|
||||
|
||||
// Only set it if it's not already SilentlyContinue
|
||||
if (!ActionPreference.SilentlyContinue.Equals(originalWarningPreference))
|
||||
{
|
||||
// Set the preference for this cmdlet execution
|
||||
SessionState.PSVariable.Set("WarningPreference", ActionPreference.SilentlyContinue);
|
||||
}
|
||||
}
|
||||
|
||||
Logger = new PowerShellLogger(this);
|
||||
|
||||
if (!OPNSenseSession.IsConnected)
|
||||
// Skip connection check for Connect-OPNSense and Get-OPNSenseConnection cmdlets
|
||||
var cmdletType = GetType();
|
||||
var sessionState = OPNSenseSessionState.Instance;
|
||||
if (cmdletType != typeof(ConnectOPNSenseCmdlet) && cmdletType != typeof(GetOPNSenseConnectionCmdlet) &&
|
||||
(sessionState.ApiClient == null || !sessionState.ApiClient.IsConnected))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new InvalidOperationException("Not connected to an OPNSense firewall. Use Connect-OPNSense first."),
|
||||
@@ -68,7 +87,7 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
/// <summary>
|
||||
/// Gets the API client
|
||||
/// </summary>
|
||||
protected OPNSenseApiClient ApiClient => OPNSenseSession.Current;
|
||||
protected OPNSenseApiClient ApiClient => OPNSenseSessionState.Instance.ApiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger
|
||||
@@ -90,7 +109,13 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
{
|
||||
// Store the exception to be processed in ProcessRecord
|
||||
ProcessingException = ex;
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
|
||||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
|
||||
{
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,44 +148,55 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an async task synchronously and safely
|
||||
/// Safely executes a function and handles exceptions
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type of the task</typeparam>
|
||||
/// <param name="asyncFunc">The async function to execute</param>
|
||||
/// <returns>The result of the async function</returns>
|
||||
protected T ExecuteAsyncTask<T>(Func<Task<T>> asyncFunc)
|
||||
/// <typeparam name="T">The return type of the function</typeparam>
|
||||
/// <param name="func">The function to execute</param>
|
||||
/// <returns>The result of the function</returns>
|
||||
protected T ExecuteSafely<T>(Func<T> func)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use ConfigureAwait(false) to avoid deadlocks
|
||||
return asyncFunc().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Store the exception to be processed in ProcessRecord
|
||||
ProcessingException = ex;
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
|
||||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
|
||||
{
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an async task synchronously and safely (no return value)
|
||||
/// Safely executes an action and handles exceptions
|
||||
/// </summary>
|
||||
/// <param name="asyncAction">The async action to execute</param>
|
||||
protected void ExecuteAsyncTask(Func<Task> asyncAction)
|
||||
/// <param name="action">The action to execute</param>
|
||||
protected void ExecuteSafely(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use ConfigureAwait(false) to avoid deadlocks
|
||||
asyncAction().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Store the exception to be processed in ProcessRecord
|
||||
ProcessingException = ex;
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (MyInvocation.BoundParameters.ContainsKey("WarningAction") ||
|
||||
!ActionPreference.SilentlyContinue.Equals(SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.Continue)))
|
||||
{
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
|
||||
namespace PSOPNSenseAPI.Cmdlets
|
||||
{
|
||||
@@ -11,7 +12,7 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsDiagnostic.Test, "OPNSenseThreading")]
|
||||
[OutputType(typeof(string))]
|
||||
public class TestThreadingCmdlet : OPNSenseBaseCmdlet
|
||||
public class TestThreadingCmdlet : PSCmdlet
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The test mode to run.</para>
|
||||
@@ -26,41 +27,101 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
[Parameter(Mandatory = false)]
|
||||
public int Delay { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be processed in ProcessRecord
|
||||
/// </summary>
|
||||
protected Exception ProcessingException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Begins the processing of the cmdlet
|
||||
/// </summary>
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
base.BeginProcessing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the cmdlet
|
||||
/// </summary>
|
||||
protected override void ProcessRecordInternal()
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
WriteVerbose($"Starting test in mode: {Mode}");
|
||||
WriteVerbose($"Current thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteVerbose($"Current synchronization context: {SynchronizationContext.Current != null}");
|
||||
|
||||
switch (Mode)
|
||||
try
|
||||
{
|
||||
case "Direct":
|
||||
TestDirect();
|
||||
break;
|
||||
case "ConfigureAwait":
|
||||
TestConfigureAwait();
|
||||
break;
|
||||
case "SynchronizationContext":
|
||||
TestSynchronizationContext();
|
||||
break;
|
||||
case "TaskRun":
|
||||
TestTaskRun();
|
||||
break;
|
||||
}
|
||||
WriteVerbose($"Starting test in mode: {Mode}");
|
||||
WriteVerbose($"Current thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteVerbose($"Current synchronization context: {SynchronizationContext.Current != null}");
|
||||
|
||||
WriteVerbose("Test completed");
|
||||
switch (Mode)
|
||||
{
|
||||
case "Direct":
|
||||
TestDirect();
|
||||
break;
|
||||
case "ConfigureAwait":
|
||||
TestConfigureAwait();
|
||||
break;
|
||||
case "SynchronizationContext":
|
||||
TestSynchronizationContext();
|
||||
break;
|
||||
case "TaskRun":
|
||||
TestTaskRun();
|
||||
break;
|
||||
}
|
||||
|
||||
WriteVerbose("Test completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
ProcessingException = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Process any exceptions that occurred
|
||||
if (ProcessingException != null)
|
||||
{
|
||||
WriteWarning($"Processing exception: {ProcessingException.Message}");
|
||||
ProcessingException = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the processing of the cmdlet
|
||||
/// </summary>
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
base.EndProcessing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an async task synchronously and safely
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type of the task</typeparam>
|
||||
/// <param name="asyncFunc">The async function to execute</param>
|
||||
/// <returns>The result of the async function</returns>
|
||||
protected T ExecuteAsyncTask<T>(Func<Task<T>> asyncFunc)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use ConfigureAwait(false) to avoid deadlocks
|
||||
return asyncFunc().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Store the exception to be processed in ProcessRecord
|
||||
ProcessingException = ex;
|
||||
WriteWarning($"Error occurred: {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private void TestDirect()
|
||||
{
|
||||
WriteVerbose("Testing direct method call");
|
||||
WriteVerbose($"Before delay - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
|
||||
|
||||
Task.Delay(Delay).Wait();
|
||||
|
||||
|
||||
WriteVerbose($"After delay - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteObject("Direct test completed");
|
||||
}
|
||||
@@ -69,13 +130,13 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
{
|
||||
WriteVerbose("Testing ConfigureAwait(false)");
|
||||
WriteVerbose($"Before async call - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
|
||||
var result = ExecuteAsyncTask(() => Task.Delay(Delay).ContinueWith(t =>
|
||||
|
||||
var result = ExecuteAsyncTask(() => Task.Delay(Delay).ContinueWith(t =>
|
||||
{
|
||||
WriteVerbose($"Inside async task - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
return "ConfigureAwait test completed";
|
||||
}));
|
||||
|
||||
|
||||
WriteVerbose($"After async call - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteObject(result);
|
||||
}
|
||||
@@ -85,24 +146,24 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
WriteVerbose("Testing SynchronizationContext");
|
||||
WriteVerbose($"Before async call - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteVerbose($"SynchronizationContext: {SynchronizationContext.Current != null}");
|
||||
|
||||
|
||||
var syncContext = SynchronizationContext.Current;
|
||||
var result = ExecuteAsyncTask(() => Task.Delay(Delay).ContinueWith(t =>
|
||||
var result = ExecuteAsyncTask(() => Task.Delay(Delay).ContinueWith(t =>
|
||||
{
|
||||
var taskThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
WriteVerbose($"Inside async task - Thread ID: {taskThreadId}");
|
||||
|
||||
|
||||
if (syncContext != null)
|
||||
{
|
||||
syncContext.Post(_ =>
|
||||
syncContext.Post(_ =>
|
||||
{
|
||||
WriteVerbose($"Inside SynchronizationContext.Post - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
}, null);
|
||||
}
|
||||
|
||||
|
||||
return "SynchronizationContext test completed";
|
||||
}));
|
||||
|
||||
|
||||
WriteVerbose($"After async call - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteObject(result);
|
||||
}
|
||||
@@ -111,17 +172,17 @@ namespace PSOPNSenseAPI.Cmdlets
|
||||
{
|
||||
WriteVerbose("Testing Task.Run");
|
||||
WriteVerbose($"Before Task.Run - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
|
||||
var task = Task.Run(() =>
|
||||
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
WriteVerbose($"Inside Task.Run - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
Thread.Sleep(Delay);
|
||||
WriteVerbose($"After delay in Task.Run - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
return "TaskRun test completed";
|
||||
});
|
||||
|
||||
|
||||
var result = task.GetAwaiter().GetResult();
|
||||
|
||||
|
||||
WriteVerbose($"After Task.Run - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
|
||||
WriteObject(result);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,33 @@ namespace PSOPNSenseAPI.Logging
|
||||
/// <param name="message">The message to log</param>
|
||||
public void Warning(string message)
|
||||
{
|
||||
_cmdlet.WriteWarning(message);
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
var cmdlet = _cmdlet as PSCmdlet;
|
||||
if (cmdlet != null)
|
||||
{
|
||||
// Check if WarningAction is explicitly set or if WarningPreference is not SilentlyContinue
|
||||
if (cmdlet.MyInvocation.BoundParameters.ContainsKey("WarningAction"))
|
||||
{
|
||||
// If WarningAction is explicitly set, respect it
|
||||
_cmdlet.WriteWarning(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the current WarningPreference
|
||||
var warningPreference = cmdlet.SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.SilentlyContinue);
|
||||
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (!ActionPreference.SilentlyContinue.Equals(warningPreference))
|
||||
{
|
||||
_cmdlet.WriteWarning(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for non-PSCmdlet contexts - suppress by default
|
||||
// _cmdlet.WriteWarning(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +79,33 @@ namespace PSOPNSenseAPI.Logging
|
||||
{
|
||||
// Store the error message but don't call WriteError directly
|
||||
// The cmdlet will handle writing the error in ProcessRecord
|
||||
_cmdlet.WriteWarning($"Error: {message}");
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
var cmdlet = _cmdlet as PSCmdlet;
|
||||
if (cmdlet != null)
|
||||
{
|
||||
// Check if WarningAction is explicitly set or if WarningPreference is not SilentlyContinue
|
||||
if (cmdlet.MyInvocation.BoundParameters.ContainsKey("WarningAction"))
|
||||
{
|
||||
// If WarningAction is explicitly set, respect it
|
||||
_cmdlet.WriteWarning($"Error: {message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the current WarningPreference
|
||||
var warningPreference = cmdlet.SessionState.PSVariable.GetValue("WarningPreference", ActionPreference.SilentlyContinue);
|
||||
|
||||
// Only write warning if WarningPreference is not SilentlyContinue
|
||||
if (!ActionPreference.SilentlyContinue.Equals(warningPreference))
|
||||
{
|
||||
_cmdlet.WriteWarning($"Error: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for non-PSCmdlet contexts - suppress by default
|
||||
// _cmdlet.WriteWarning($"Error: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
RootModule = 'lib\PSOPNSenseAPI.dll'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '2025.04.15.1740'
|
||||
ModuleVersion = '2025.04.18.1015'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = @('Desktop', 'Core')
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
|
||||
@@ -12,6 +11,7 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
private readonly OPNSenseApiClient _apiClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly OPNSenseApiEndpoints _apiEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AliasService"/> class
|
||||
@@ -22,18 +22,19 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
_logger = logger;
|
||||
_apiEndpoints = OPNSenseSessionState.Instance.ApiEndpoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all aliases
|
||||
/// </summary>
|
||||
/// <returns>A list of aliases</returns>
|
||||
public async Task<AliasListResponse> GetAliasesAsync()
|
||||
public AliasListResponse GetAliases()
|
||||
{
|
||||
_logger.Information("Getting aliases");
|
||||
|
||||
var endpoint = "firewall/alias/searchItem";
|
||||
return await _apiClient.GetAsync<AliasListResponse>(endpoint);
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.list");
|
||||
return _apiClient.Get<AliasListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,12 +42,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID of the alias</param>
|
||||
/// <returns>The alias</returns>
|
||||
public async Task<AliasResponse> GetAliasAsync(string uuid)
|
||||
public AliasResponse GetAlias(string uuid)
|
||||
{
|
||||
_logger.Information($"Getting alias with UUID {uuid}");
|
||||
|
||||
var endpoint = $"firewall/alias/getItem/{uuid}";
|
||||
return await _apiClient.GetAsync<AliasResponse>(endpoint);
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.get", uuid);
|
||||
return _apiClient.Get<AliasResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,13 +55,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias to create</param>
|
||||
/// <returns>The response containing the UUID of the new alias</returns>
|
||||
public async Task<AliasCreateResponse> CreateAliasAsync(AliasConfig alias)
|
||||
public AliasCreateResponse CreateAlias(AliasConfig alias)
|
||||
{
|
||||
_logger.Information("Creating alias");
|
||||
|
||||
var endpoint = "firewall/alias/addItem";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.add");
|
||||
var data = new { alias = alias };
|
||||
return await _apiClient.PostAsync<AliasCreateResponse>(endpoint, data);
|
||||
return _apiClient.Post<AliasCreateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -69,13 +70,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="uuid">The UUID of the alias to update</param>
|
||||
/// <param name="alias">The updated alias</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<AliasUpdateResponse> UpdateAliasAsync(string uuid, AliasConfig alias)
|
||||
public AliasUpdateResponse UpdateAlias(string uuid, AliasConfig alias)
|
||||
{
|
||||
_logger.Information($"Updating alias with UUID {uuid}");
|
||||
|
||||
var endpoint = $"firewall/alias/setItem/{uuid}";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.update", uuid);
|
||||
var data = new { alias = alias };
|
||||
return await _apiClient.PostAsync<AliasUpdateResponse>(endpoint, data);
|
||||
return _apiClient.Post<AliasUpdateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,24 +84,24 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID of the alias to delete</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<AliasDeleteResponse> DeleteAliasAsync(string uuid)
|
||||
public AliasDeleteResponse DeleteAlias(string uuid)
|
||||
{
|
||||
_logger.Information($"Deleting alias with UUID {uuid}");
|
||||
|
||||
var endpoint = $"firewall/alias/delItem/{uuid}";
|
||||
return await _apiClient.PostAsync<AliasDeleteResponse>(endpoint);
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.delete", uuid);
|
||||
return _apiClient.Post<AliasDeleteResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconfigures aliases
|
||||
/// </summary>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<AliasReconfigureResponse> ReconfigureAliasesAsync()
|
||||
public AliasReconfigureResponse ReconfigureAliases()
|
||||
{
|
||||
_logger.Information("Reconfiguring aliases");
|
||||
|
||||
var endpoint = "firewall/alias/reconfigure";
|
||||
return await _apiClient.PostAsync<AliasReconfigureResponse>(endpoint);
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("firewall.alias.reconfigure");
|
||||
return _apiClient.Post<AliasReconfigureResponse>(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,3 +345,4 @@ namespace PSOPNSenseAPI.Services
|
||||
public string Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
@@ -14,6 +13,7 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
private readonly OPNSenseApiClient _apiClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly OPNSenseApiEndpoints _apiEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigService"/> class
|
||||
@@ -24,18 +24,19 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
_logger = logger;
|
||||
_apiEndpoints = OPNSenseSessionState.Instance.ApiEndpoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of configuration backups
|
||||
/// </summary>
|
||||
/// <returns>A list of configuration backups</returns>
|
||||
public async Task<ConfigBackupListResponse> GetConfigBackupsAsync()
|
||||
public ConfigBackupListResponse GetConfigBackups()
|
||||
{
|
||||
_logger.Information("Getting configuration backups");
|
||||
|
||||
var endpoint = "core/backup/getBackups";
|
||||
return await _apiClient.GetAsync<ConfigBackupListResponse>(endpoint);
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.backup.list");
|
||||
return _apiClient.Get<ConfigBackupListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,13 +44,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename for the backup</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<ConfigBackupCreateResponse> CreateConfigBackupAsync(string filename = null)
|
||||
public ConfigBackupCreateResponse CreateConfigBackup(string filename = null)
|
||||
{
|
||||
_logger.Information("Creating configuration backup");
|
||||
|
||||
var endpoint = "core/backup/backup";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.backup.create");
|
||||
var data = string.IsNullOrEmpty(filename) ? null : new { filename = filename };
|
||||
return await _apiClient.PostAsync<ConfigBackupCreateResponse>(endpoint, data);
|
||||
return _apiClient.Post<ConfigBackupCreateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,18 +58,18 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename of the backup to download</param>
|
||||
/// <returns>The backup content</returns>
|
||||
public async Task<byte[]> DownloadConfigBackupAsync(string filename)
|
||||
public byte[] DownloadConfigBackup(string filename)
|
||||
{
|
||||
_logger.Information($"Downloading configuration backup {filename}");
|
||||
|
||||
var endpoint = $"core/backup/download/{filename}";
|
||||
var response = await _apiClient.GetAsync<ConfigBackupDownloadResponse>(endpoint);
|
||||
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.backup.download", filename);
|
||||
var response = _apiClient.Get<ConfigBackupDownloadResponse>(endpoint);
|
||||
|
||||
if (string.IsNullOrEmpty(response.Content))
|
||||
{
|
||||
throw new OPNSenseApiException("Backup content is empty", System.Net.HttpStatusCode.NoContent, "");
|
||||
}
|
||||
|
||||
|
||||
return Convert.FromBase64String(response.Content);
|
||||
}
|
||||
|
||||
@@ -77,13 +78,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename of the backup to restore</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<ConfigBackupRestoreResponse> RestoreConfigBackupAsync(string filename)
|
||||
public ConfigBackupRestoreResponse RestoreConfigBackup(string filename)
|
||||
{
|
||||
_logger.Information($"Restoring configuration backup {filename}");
|
||||
|
||||
var endpoint = "core/backup/restore";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.backup.restore");
|
||||
var data = new { filename = filename };
|
||||
return await _apiClient.PostAsync<ConfigBackupRestoreResponse>(endpoint, data);
|
||||
return _apiClient.Post<ConfigBackupRestoreResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,31 +92,31 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename of the backup to delete</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<ConfigBackupDeleteResponse> DeleteConfigBackupAsync(string filename)
|
||||
public ConfigBackupDeleteResponse DeleteConfigBackup(string filename)
|
||||
{
|
||||
_logger.Information($"Deleting configuration backup {filename}");
|
||||
|
||||
var endpoint = "core/backup/deleteBackup";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.backup.delete");
|
||||
var data = new { filename = filename };
|
||||
return await _apiClient.PostAsync<ConfigBackupDeleteResponse>(endpoint, data);
|
||||
return _apiClient.Post<ConfigBackupDeleteResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the configuration
|
||||
/// </summary>
|
||||
/// <returns>The exported configuration</returns>
|
||||
public async Task<byte[]> ExportConfigAsync()
|
||||
public byte[] ExportConfig()
|
||||
{
|
||||
_logger.Information("Exporting configuration");
|
||||
|
||||
var endpoint = "core/backup/download";
|
||||
var response = await _apiClient.GetAsync<ConfigExportResponse>(endpoint);
|
||||
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.export");
|
||||
var response = _apiClient.Get<ConfigExportResponse>(endpoint);
|
||||
|
||||
if (string.IsNullOrEmpty(response.Content))
|
||||
{
|
||||
throw new OPNSenseApiException("Export content is empty", System.Net.HttpStatusCode.NoContent, "");
|
||||
}
|
||||
|
||||
|
||||
return Convert.FromBase64String(response.Content);
|
||||
}
|
||||
|
||||
@@ -124,14 +125,14 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="configContent">The configuration content</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<ConfigImportResponse> ImportConfigAsync(byte[] configContent)
|
||||
public ConfigImportResponse ImportConfig(byte[] configContent)
|
||||
{
|
||||
_logger.Information("Importing configuration");
|
||||
|
||||
var endpoint = "core/backup/upload";
|
||||
|
||||
var endpoint = _apiEndpoints.GetEndpoint("config.import");
|
||||
var base64Content = Convert.ToBase64String(configContent);
|
||||
var data = new { content = base64Content };
|
||||
return await _apiClient.PostAsync<ConfigImportResponse>(endpoint, data);
|
||||
return _apiClient.Post<ConfigImportResponse>(endpoint, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,3 +262,4 @@ namespace PSOPNSenseAPI.Services
|
||||
public string Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
|
||||
namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
@@ -12,6 +12,7 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
private readonly OPNSenseApiClient _apiClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly OPNSenseApiEndpoints _apiEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DHCPService"/> class
|
||||
@@ -22,18 +23,21 @@ namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
_logger = logger;
|
||||
_apiEndpoints = OPNSenseSessionState.Instance.ApiEndpoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all DHCP leases
|
||||
/// </summary>
|
||||
/// <returns>A list of DHCP leases</returns>
|
||||
public async Task<DHCPLeaseListResponse> GetLeasesAsync()
|
||||
public DHCPLeaseListResponse GetLeases()
|
||||
{
|
||||
_logger.Information("Getting DHCP leases");
|
||||
|
||||
var endpoint = "dhcp/leases/searchLease";
|
||||
return await _apiClient.GetAsync<DHCPLeaseListResponse>(endpoint);
|
||||
var endpoint = _apiEndpoints.GetEndpoint("dhcp.leases.list");
|
||||
var fullUrl = _apiEndpoints.GetFullUrl("dhcp.leases.list");
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
return _apiClient.Get<DHCPLeaseListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,12 +45,14 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="macAddress">The MAC address of the lease</param>
|
||||
/// <returns>The DHCP lease</returns>
|
||||
public async Task<DHCPLeaseResponse> GetLeaseByMacAsync(string macAddress)
|
||||
public DHCPLeaseResponse GetLeaseByMac(string macAddress)
|
||||
{
|
||||
_logger.Information($"Getting DHCP lease for MAC address {macAddress}");
|
||||
|
||||
var endpoint = $"dhcp/leases/getLeaseByMac/{macAddress}";
|
||||
return await _apiClient.GetAsync<DHCPLeaseResponse>(endpoint);
|
||||
var endpoint = _apiEndpoints.GetEndpoint("dhcp.leases.getByMac", macAddress);
|
||||
var fullUrl = _apiEndpoints.GetFullUrl("dhcp.leases.getByMac", macAddress);
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
return _apiClient.Get<DHCPLeaseResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,12 +60,14 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="macAddress">The MAC address of the lease to delete</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPLeaseDeleteResponse> DeleteLeaseAsync(string macAddress)
|
||||
public DHCPLeaseDeleteResponse DeleteLease(string macAddress)
|
||||
{
|
||||
_logger.Information($"Deleting DHCP lease for MAC address {macAddress}");
|
||||
|
||||
var endpoint = $"dhcp/leases/delLease/{macAddress}";
|
||||
return await _apiClient.PostAsync<DHCPLeaseDeleteResponse>(endpoint);
|
||||
var endpoint = _apiEndpoints.GetEndpoint("dhcp.leases.delete", macAddress);
|
||||
var fullUrl = _apiEndpoints.GetFullUrl("dhcp.leases.delete", macAddress);
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
return _apiClient.Post<DHCPLeaseDeleteResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,23 +76,25 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="lease">The lease to add</param>
|
||||
/// <returns>The response containing the UUID of the new lease</returns>
|
||||
public async Task<DHCPStaticMappingCreateResponse> AddStaticLeaseAsync(string @interface, DHCPStaticMappingConfig lease)
|
||||
public DHCPStaticMappingCreateResponse AddStaticLease(string @interface, DHCPStaticMappingConfig lease)
|
||||
{
|
||||
_logger.Information($"Adding static DHCP lease for interface {@interface}");
|
||||
|
||||
return await CreateStaticMappingAsync(@interface, lease);
|
||||
return CreateStaticMapping(@interface, lease);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all DHCP servers
|
||||
/// </summary>
|
||||
/// <returns>A list of DHCP servers</returns>
|
||||
public async Task<DHCPServerListResponse> GetServersAsync()
|
||||
public DHCPServerListResponse GetServers()
|
||||
{
|
||||
_logger.Information("Getting DHCP servers");
|
||||
|
||||
var endpoint = "dhcp/service/get";
|
||||
return await _apiClient.GetAsync<DHCPServerListResponse>(endpoint);
|
||||
var endpoint = "dhcpd/service/get";
|
||||
var fullUrl = _apiClient.GetFullUrl(endpoint);
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
return _apiClient.Get<DHCPServerListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,12 +102,14 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <returns>The DHCP server</returns>
|
||||
public async Task<DHCPServerResponse> GetServerAsync(string @interface)
|
||||
public DHCPServerResponse GetServer(string @interface)
|
||||
{
|
||||
_logger.Information($"Getting DHCP server for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/getServer/{@interface}";
|
||||
return await _apiClient.GetAsync<DHCPServerResponse>(endpoint);
|
||||
var endpoint = $"dhcpd/service/getServer/{@interface}";
|
||||
var fullUrl = _apiClient.GetFullUrl(endpoint);
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
return _apiClient.Get<DHCPServerResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,13 +118,15 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="server">The updated server</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPServerUpdateResponse> UpdateServerAsync(string @interface, DHCPServerConfig server)
|
||||
public DHCPServerUpdateResponse UpdateServer(string @interface, DHCPServerConfig server)
|
||||
{
|
||||
_logger.Information($"Updating DHCP server for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/setServer/{@interface}";
|
||||
var endpoint = $"dhcpd/service/setServer/{@interface}";
|
||||
var fullUrl = _apiClient.GetFullUrl(endpoint);
|
||||
_logger.Verbose($"API URL: {fullUrl}");
|
||||
var data = new { server = server };
|
||||
return await _apiClient.PostAsync<DHCPServerUpdateResponse>(endpoint, data);
|
||||
return _apiClient.Post<DHCPServerUpdateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,12 +134,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPServerEnableResponse> EnableServerAsync(string @interface)
|
||||
public DHCPServerEnableResponse EnableServer(string @interface)
|
||||
{
|
||||
_logger.Information($"Enabling DHCP server for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/enableServer/{@interface}";
|
||||
return await _apiClient.PostAsync<DHCPServerEnableResponse>(endpoint);
|
||||
return _apiClient.Post<DHCPServerEnableResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,12 +147,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPServerDisableResponse> DisableServerAsync(string @interface)
|
||||
public DHCPServerDisableResponse DisableServer(string @interface)
|
||||
{
|
||||
_logger.Information($"Disabling DHCP server for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/disableServer/{@interface}";
|
||||
return await _apiClient.PostAsync<DHCPServerDisableResponse>(endpoint);
|
||||
return _apiClient.Post<DHCPServerDisableResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -146,12 +160,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <returns>A list of static mappings</returns>
|
||||
public async Task<DHCPStaticMappingListResponse> GetStaticMappingsAsync(string @interface)
|
||||
public DHCPStaticMappingListResponse GetStaticMappings(string @interface)
|
||||
{
|
||||
_logger.Information($"Getting static mappings for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/searchStaticMapping/{@interface}";
|
||||
return await _apiClient.GetAsync<DHCPStaticMappingListResponse>(endpoint);
|
||||
return _apiClient.Get<DHCPStaticMappingListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,12 +174,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="uuid">The UUID of the static mapping</param>
|
||||
/// <returns>The static mapping</returns>
|
||||
public async Task<DHCPStaticMappingResponse> GetStaticMappingAsync(string @interface, string uuid)
|
||||
public DHCPStaticMappingResponse GetStaticMapping(string @interface, string uuid)
|
||||
{
|
||||
_logger.Information($"Getting static mapping with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/getStaticMapping/{@interface}/{uuid}";
|
||||
return await _apiClient.GetAsync<DHCPStaticMappingResponse>(endpoint);
|
||||
return _apiClient.Get<DHCPStaticMappingResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,13 +188,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="mapping">The static mapping to create</param>
|
||||
/// <returns>The response containing the UUID of the new static mapping</returns>
|
||||
public async Task<DHCPStaticMappingCreateResponse> CreateStaticMappingAsync(string @interface, DHCPStaticMappingConfig mapping)
|
||||
public DHCPStaticMappingCreateResponse CreateStaticMapping(string @interface, DHCPStaticMappingConfig mapping)
|
||||
{
|
||||
_logger.Information($"Creating static mapping for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/addStaticMapping/{@interface}";
|
||||
var data = new { mapping = mapping };
|
||||
return await _apiClient.PostAsync<DHCPStaticMappingCreateResponse>(endpoint, data);
|
||||
return _apiClient.Post<DHCPStaticMappingCreateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,13 +204,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="uuid">The UUID of the static mapping to update</param>
|
||||
/// <param name="mapping">The updated static mapping</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPStaticMappingUpdateResponse> UpdateStaticMappingAsync(string @interface, string uuid, DHCPStaticMappingConfig mapping)
|
||||
public DHCPStaticMappingUpdateResponse UpdateStaticMapping(string @interface, string uuid, DHCPStaticMappingConfig mapping)
|
||||
{
|
||||
_logger.Information($"Updating static mapping with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/setStaticMapping/{@interface}/{uuid}";
|
||||
var data = new { mapping = mapping };
|
||||
return await _apiClient.PostAsync<DHCPStaticMappingUpdateResponse>(endpoint, data);
|
||||
return _apiClient.Post<DHCPStaticMappingUpdateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,12 +219,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="uuid">The UUID of the static mapping to delete</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPStaticMappingDeleteResponse> DeleteStaticMappingAsync(string @interface, string uuid)
|
||||
public DHCPStaticMappingDeleteResponse DeleteStaticMapping(string @interface, string uuid)
|
||||
{
|
||||
_logger.Information($"Deleting static mapping with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/delStaticMapping/{@interface}/{uuid}";
|
||||
return await _apiClient.PostAsync<DHCPStaticMappingDeleteResponse>(endpoint);
|
||||
return _apiClient.Post<DHCPStaticMappingDeleteResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -218,12 +232,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <returns>The DHCP options</returns>
|
||||
public async Task<DHCPOptionsListResponse> GetOptionsAsync(string @interface)
|
||||
public DHCPOptionsListResponse GetOptions(string @interface)
|
||||
{
|
||||
_logger.Information($"Getting DHCP options for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/searchOptions/{@interface}";
|
||||
return await _apiClient.GetAsync<DHCPOptionsListResponse>(endpoint);
|
||||
return _apiClient.Get<DHCPOptionsListResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -232,12 +246,12 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="uuid">The UUID of the option</param>
|
||||
/// <returns>The DHCP option</returns>
|
||||
public async Task<DHCPOptionResponse> GetOptionAsync(string @interface, string uuid)
|
||||
public DHCPOptionResponse GetOption(string @interface, string uuid)
|
||||
{
|
||||
_logger.Information($"Getting DHCP option with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/getOption/{@interface}/{uuid}";
|
||||
return await _apiClient.GetAsync<DHCPOptionResponse>(endpoint);
|
||||
return _apiClient.Get<DHCPOptionResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -246,13 +260,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="option">The option to create</param>
|
||||
/// <returns>The response containing the UUID of the new option</returns>
|
||||
public async Task<DHCPOptionCreateResponse> CreateOptionAsync(string @interface, DHCPOptionConfig option)
|
||||
public DHCPOptionCreateResponse CreateOption(string @interface, DHCPOptionConfig option)
|
||||
{
|
||||
_logger.Information($"Creating DHCP option for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/addOption/{@interface}";
|
||||
var data = new { option = option };
|
||||
return await _apiClient.PostAsync<DHCPOptionCreateResponse>(endpoint, data);
|
||||
return _apiClient.Post<DHCPOptionCreateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -262,13 +276,13 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="uuid">The UUID of the option to update</param>
|
||||
/// <param name="option">The updated option</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPOptionUpdateResponse> UpdateOptionAsync(string @interface, string uuid, DHCPOptionConfig option)
|
||||
public DHCPOptionUpdateResponse UpdateOption(string @interface, string uuid, DHCPOptionConfig option)
|
||||
{
|
||||
_logger.Information($"Updating DHCP option with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/setOption/{@interface}/{uuid}";
|
||||
var data = new { option = option };
|
||||
return await _apiClient.PostAsync<DHCPOptionUpdateResponse>(endpoint, data);
|
||||
return _apiClient.Post<DHCPOptionUpdateResponse>(endpoint, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -277,24 +291,24 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="interface">The interface name</param>
|
||||
/// <param name="uuid">The UUID of the option to delete</param>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPOptionDeleteResponse> DeleteOptionAsync(string @interface, string uuid)
|
||||
public DHCPOptionDeleteResponse DeleteOption(string @interface, string uuid)
|
||||
{
|
||||
_logger.Information($"Deleting DHCP option with UUID {uuid} for interface {@interface}");
|
||||
|
||||
var endpoint = $"dhcp/service/delOption/{@interface}/{uuid}";
|
||||
return await _apiClient.PostAsync<DHCPOptionDeleteResponse>(endpoint);
|
||||
return _apiClient.Post<DHCPOptionDeleteResponse>(endpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies DHCP changes
|
||||
/// </summary>
|
||||
/// <returns>The response indicating success</returns>
|
||||
public async Task<DHCPApplyResponse> ApplyChangesAsync()
|
||||
public DHCPApplyResponse ApplyChanges()
|
||||
{
|
||||
_logger.Information("Applying DHCP changes");
|
||||
|
||||
var endpoint = "dhcp/service/reconfigure";
|
||||
return await _apiClient.PostAsync<DHCPApplyResponse>(endpoint);
|
||||
return _apiClient.Post<DHCPApplyResponse>(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1066,3 +1080,4 @@ namespace PSOPNSenseAPI.Services
|
||||
public string Result { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
@@ -43,6 +42,16 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full URL for the given endpoint
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint</param>
|
||||
/// <returns>The full URL</returns>
|
||||
public string GetFullUrl(string endpoint)
|
||||
{
|
||||
return $"{BaseUrl}/api/{endpoint.TrimStart('/')}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OPNSenseApiClient"/> class
|
||||
/// </summary>
|
||||
@@ -92,15 +101,15 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <typeparam name="T">The type to deserialize the response to</typeparam>
|
||||
/// <param name="endpoint">The API endpoint</param>
|
||||
/// <returns>The deserialized response</returns>
|
||||
public async Task<T> GetAsync<T>(string endpoint)
|
||||
public T Get<T>(string endpoint)
|
||||
{
|
||||
var url = $"{BaseUrl}/api/{endpoint.TrimStart('/')}";
|
||||
_logger.Debug($"GET {url}");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
return await ProcessResponseAsync<T>(response);
|
||||
var response = _httpClient.GetAsync(url).GetAwaiter().GetResult();
|
||||
return ProcessResponse<T>(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -116,7 +125,7 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <param name="endpoint">The API endpoint</param>
|
||||
/// <param name="data">The data to send</param>
|
||||
/// <returns>The deserialized response</returns>
|
||||
public async Task<T> PostAsync<T>(string endpoint, object data = null)
|
||||
public T Post<T>(string endpoint, object data = null)
|
||||
{
|
||||
var url = $"{BaseUrl}/api/{endpoint.TrimStart('/')}";
|
||||
_logger.Debug($"POST {url}");
|
||||
@@ -131,8 +140,8 @@ namespace PSOPNSenseAPI.Services
|
||||
content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync(url, content);
|
||||
return await ProcessResponseAsync<T>(response);
|
||||
var response = _httpClient.PostAsync(url, content).GetAwaiter().GetResult();
|
||||
return ProcessResponse<T>(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -147,9 +156,9 @@ namespace PSOPNSenseAPI.Services
|
||||
/// <typeparam name="T">The type to deserialize the response to</typeparam>
|
||||
/// <param name="response">The HTTP response</param>
|
||||
/// <returns>The deserialized response</returns>
|
||||
private async Task<T> ProcessResponseAsync<T>(HttpResponseMessage response)
|
||||
private T ProcessResponse<T>(HttpResponseMessage response)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
|
||||
_logger.Debug($"Response status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
_logger.Debug($"Response body: {content}");
|
||||
@@ -201,3 +210,4 @@ namespace PSOPNSenseAPI.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
|
||||
namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides centralized mapping of API endpoints for different OPNSense versions
|
||||
/// </summary>
|
||||
public class OPNSenseApiEndpoints
|
||||
{
|
||||
private readonly OPNSenseApiClient _apiClient;
|
||||
private readonly ILogger _logger;
|
||||
private OPNSenseVersion? _detectedVersion;
|
||||
|
||||
|
||||
// Dictionary of endpoint mappings
|
||||
private static readonly Dictionary<string, Dictionary<OPNSenseVersion, string>> EndpointMappings = new Dictionary<string, Dictionary<OPNSenseVersion, string>>
|
||||
{
|
||||
// DHCP endpoints
|
||||
{ "dhcp.leases.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "dhcpd/leases/searchLease" },
|
||||
{ OPNSenseVersion.Modern, "dhcpd/service/searchLease" }
|
||||
}},
|
||||
{ "dhcp.leases.getByMac", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "dhcpd/leases/getLeaseByMac/{0}" },
|
||||
{ OPNSenseVersion.Modern, "dhcpd/service/getLeaseByMac/{0}" }
|
||||
}},
|
||||
{ "dhcp.leases.delete", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "dhcpd/leases/delLease/{0}" },
|
||||
{ OPNSenseVersion.Modern, "dhcpd/service/delLease/{0}" }
|
||||
}},
|
||||
|
||||
// Config/Backup endpoints
|
||||
{ "config.backup.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/backup/getBackups" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/getBackups" }
|
||||
}},
|
||||
{ "config.backup.create", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/backup/backup" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/backup" }
|
||||
}},
|
||||
{ "config.backup.download", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/backup/download/{0}" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/download/{0}" }
|
||||
}},
|
||||
{ "config.backup.restore", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/backup/restore" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/restore" }
|
||||
}},
|
||||
{ "config.backup.delete", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/backup/deleteBackup" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/deleteBackup" }
|
||||
}},
|
||||
{ "config.export", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/config/download" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/download" }
|
||||
}},
|
||||
{ "config.import", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/config/upload" },
|
||||
{ OPNSenseVersion.Modern, "core/backup/upload" }
|
||||
}},
|
||||
|
||||
// Firewall/Alias endpoints
|
||||
{ "firewall.alias.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/searchItem" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/searchItem" }
|
||||
}},
|
||||
{ "firewall.alias.get", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/getItem/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/getItem/{0}" }
|
||||
}},
|
||||
{ "firewall.alias.add", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/addItem" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/addItem" }
|
||||
}},
|
||||
{ "firewall.alias.update", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/setItem/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/setItem/{0}" }
|
||||
}},
|
||||
{ "firewall.alias.delete", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/delItem/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/delItem/{0}" }
|
||||
}},
|
||||
{ "firewall.alias.reconfigure", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/alias/reconfigure" },
|
||||
{ OPNSenseVersion.Modern, "firewall/alias/reconfigure" }
|
||||
}},
|
||||
|
||||
// Interface endpoints
|
||||
{ "interface.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "interfaces/overview/searchItem" },
|
||||
{ OPNSenseVersion.Modern, "interfaces/overview/searchItem" }
|
||||
}},
|
||||
{ "interface.get", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "interfaces/overview/getItem/{0}" },
|
||||
{ OPNSenseVersion.Modern, "interfaces/overview/getItem/{0}" }
|
||||
}},
|
||||
|
||||
// System endpoints
|
||||
{ "system.status", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/system/status" },
|
||||
{ OPNSenseVersion.Modern, "core/system/status" }
|
||||
}},
|
||||
{ "system.version", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/firmware/status" },
|
||||
{ OPNSenseVersion.Modern, "core/firmware/status" }
|
||||
}},
|
||||
|
||||
// Plugin endpoints
|
||||
{ "plugin.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/firmware/plugins" },
|
||||
{ OPNSenseVersion.Modern, "core/firmware/plugins" }
|
||||
}},
|
||||
{ "plugin.install", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "core/firmware/install/{0}" },
|
||||
{ OPNSenseVersion.Modern, "core/firmware/install/{0}" }
|
||||
}},
|
||||
|
||||
// Tailscale endpoints
|
||||
{ "tailscale.status", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "os-tailscale/service/status" },
|
||||
{ OPNSenseVersion.Modern, "os-tailscale/service/status" }
|
||||
}},
|
||||
{ "tailscale.enable", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "os-tailscale/service/start" },
|
||||
{ OPNSenseVersion.Modern, "os-tailscale/service/start" }
|
||||
}},
|
||||
{ "tailscale.disable", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "os-tailscale/service/stop" },
|
||||
{ OPNSenseVersion.Modern, "os-tailscale/service/stop" }
|
||||
}},
|
||||
{ "tailscale.restart", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "os-tailscale/service/restart" },
|
||||
{ OPNSenseVersion.Modern, "os-tailscale/service/restart" }
|
||||
}},
|
||||
|
||||
// Port forwarding endpoints
|
||||
{ "portforward.list", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/searchRule" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/searchRule" }
|
||||
}},
|
||||
{ "portforward.get", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/getRule/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/getRule/{0}" }
|
||||
}},
|
||||
{ "portforward.add", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/addRule" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/addRule" }
|
||||
}},
|
||||
{ "portforward.update", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/setRule/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/setRule/{0}" }
|
||||
}},
|
||||
{ "portforward.delete", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/delRule/{0}" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/delRule/{0}" }
|
||||
}},
|
||||
{ "portforward.apply", new Dictionary<OPNSenseVersion, string> {
|
||||
{ OPNSenseVersion.Legacy, "firewall/nat/apply" },
|
||||
{ OPNSenseVersion.Modern, "firewall/nat/apply" }
|
||||
}},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OPNSenseApiEndpoints"/> class
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The API client</param>
|
||||
/// <param name="logger">The logger</param>
|
||||
public OPNSenseApiEndpoints(OPNSenseApiClient apiClient, ILogger logger)
|
||||
{
|
||||
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the appropriate endpoint for the given key
|
||||
/// </summary>
|
||||
/// <param name="endpointKey">The endpoint key</param>
|
||||
/// <param name="parameters">Optional parameters to format into the endpoint</param>
|
||||
/// <returns>The endpoint</returns>
|
||||
public string GetEndpoint(string endpointKey, params object[] parameters)
|
||||
{
|
||||
if (!EndpointMappings.TryGetValue(endpointKey, out var versionMappings))
|
||||
{
|
||||
throw new ArgumentException($"Unknown endpoint key: {endpointKey}");
|
||||
}
|
||||
|
||||
var version = GetOPNSenseVersion();
|
||||
if (!versionMappings.TryGetValue(version, out var endpoint))
|
||||
{
|
||||
throw new ArgumentException($"No endpoint mapping found for key {endpointKey} and version {version}");
|
||||
}
|
||||
|
||||
// Format the endpoint with the provided parameters
|
||||
return parameters.Length > 0 ? string.Format(endpoint, parameters) : endpoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full URL for the given endpoint key
|
||||
/// </summary>
|
||||
/// <param name="endpointKey">The endpoint key</param>
|
||||
/// <param name="parameters">Optional parameters to format into the endpoint</param>
|
||||
/// <returns>The full URL</returns>
|
||||
public string GetFullUrl(string endpointKey, params object[] parameters)
|
||||
{
|
||||
var endpoint = GetEndpoint(endpointKey, parameters);
|
||||
return $"{_apiClient.BaseUrl}/api/{endpoint}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OPNSense version
|
||||
/// </summary>
|
||||
/// <returns>The OPNSense version</returns>
|
||||
public OPNSenseVersion GetOPNSenseVersion()
|
||||
{
|
||||
// If already detected, return the cached version
|
||||
if (_detectedVersion.HasValue)
|
||||
{
|
||||
return _detectedVersion.Value;
|
||||
}
|
||||
|
||||
// Try to detect the version
|
||||
try
|
||||
{
|
||||
_logger.Information("Detecting OPNSense version");
|
||||
|
||||
// Try to access the firmware status endpoint
|
||||
var endpoint = "core/firmware/status";
|
||||
var response = _apiClient.Get<ApiVersionResponse>(endpoint);
|
||||
|
||||
// If we get here, the API is available
|
||||
var version = response.GetVersion();
|
||||
_logger.Information($"Detected OPNSense version: {version}");
|
||||
|
||||
// Parse the version to determine if it's modern or legacy
|
||||
if (!string.IsNullOrEmpty(version) && Version.TryParse(version, out var parsedVersion))
|
||||
{
|
||||
// OPNSense 21.7 and later use the modern API
|
||||
if (parsedVersion.Major > 21 || (parsedVersion.Major == 21 && parsedVersion.Minor >= 7))
|
||||
{
|
||||
_detectedVersion = OPNSenseVersion.Modern;
|
||||
}
|
||||
else
|
||||
{
|
||||
_detectedVersion = OPNSenseVersion.Legacy;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try alternative detection methods
|
||||
_detectedVersion = DetectVersionByEndpointAvailability();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to detect OPNSense version from firmware status: {ex.Message}. Trying alternative methods.");
|
||||
_detectedVersion = DetectVersionByEndpointAvailability();
|
||||
}
|
||||
|
||||
return _detectedVersion.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the OPNSense version by checking the availability of specific endpoints
|
||||
/// </summary>
|
||||
/// <returns>The detected OPNSense version</returns>
|
||||
private OPNSenseVersion DetectVersionByEndpointAvailability()
|
||||
{
|
||||
_logger.Information("Detecting OPNSense version by endpoint availability");
|
||||
|
||||
try
|
||||
{
|
||||
// Try a modern API endpoint first (dhcpd/service/searchLease)
|
||||
var modernEndpoint = "dhcpd/service/searchLease";
|
||||
_apiClient.Get<object>(modernEndpoint);
|
||||
|
||||
// If we get here, the modern API is available
|
||||
_logger.Information("Modern API endpoint is available");
|
||||
return OPNSenseVersion.Modern;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try a legacy API endpoint (dhcpd/leases/searchLease)
|
||||
var legacyEndpoint = "dhcpd/leases/searchLease";
|
||||
_apiClient.Get<object>(legacyEndpoint);
|
||||
|
||||
// If we get here, the legacy API is available
|
||||
_logger.Information("Legacy API endpoint is available");
|
||||
return OPNSenseVersion.Legacy;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If both fail, default to legacy
|
||||
_logger.Warning("Could not determine API version by endpoint availability. Defaulting to legacy API.");
|
||||
return OPNSenseVersion.Legacy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OPNSense API version
|
||||
/// </summary>
|
||||
public enum OPNSenseVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy API (OPNSense 21.6 and earlier)
|
||||
/// </summary>
|
||||
Legacy,
|
||||
|
||||
/// <summary>
|
||||
/// Modern API (OPNSense 21.7 and later)
|
||||
/// </summary>
|
||||
Modern
|
||||
}
|
||||
|
||||
// Internal classes for version detection
|
||||
internal class ApiVersionResponse
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public object Status { get; set; }
|
||||
|
||||
[JsonProperty("firmware")]
|
||||
public ApiVersionFirmware Firmware { get; set; }
|
||||
|
||||
[JsonProperty("product_id")]
|
||||
public string ProductId { get; set; }
|
||||
|
||||
[JsonProperty("product_name")]
|
||||
public string ProductName { get; set; }
|
||||
|
||||
[JsonProperty("product_version")]
|
||||
public string ProductVersion { get; set; }
|
||||
|
||||
[JsonProperty("product_copyright")]
|
||||
public string ProductCopyright { get; set; }
|
||||
|
||||
[JsonProperty("os_version")]
|
||||
public string OsVersion { get; set; }
|
||||
|
||||
public string GetVersion()
|
||||
{
|
||||
// Try to get version from different possible locations
|
||||
if (!string.IsNullOrEmpty(ProductVersion))
|
||||
{
|
||||
return ProductVersion;
|
||||
}
|
||||
|
||||
if (Status is Newtonsoft.Json.Linq.JObject statusObj)
|
||||
{
|
||||
if (statusObj["firmware"] is Newtonsoft.Json.Linq.JObject firmwareObj &&
|
||||
firmwareObj["version"] != null)
|
||||
{
|
||||
return firmwareObj["version"].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (Firmware != null && !string.IsNullOrEmpty(Firmware.Version))
|
||||
{
|
||||
return Firmware.Version;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ApiVersionFirmware
|
||||
{
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using Newtonsoft.Json;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
using PSOPNSenseAPI.Models;
|
||||
|
||||
namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides detailed information about an OPNSense connection
|
||||
/// </summary>
|
||||
public class OPNSenseConnectionInfo
|
||||
{
|
||||
private readonly OPNSenseApiClient _apiClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly OPNSenseApiEndpoints _apiEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OPNSenseConnectionInfo"/> class
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The API client</param>
|
||||
/// <param name="logger">The logger</param>
|
||||
public OPNSenseConnectionInfo(OPNSenseApiClient apiClient, ILogger logger)
|
||||
{
|
||||
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_apiEndpoints = OPNSenseSessionState.Instance.ApiEndpoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a PSObject containing detailed connection information
|
||||
/// </summary>
|
||||
/// <returns>A PSObject with connection details</returns>
|
||||
public PSObject GetConnectionInfo()
|
||||
{
|
||||
var connectionInfo = new PSObject();
|
||||
|
||||
// Basic connection info
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Server", _apiClient.BaseUrl));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Connected", _apiClient.IsConnected));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("ApiVersion", _apiEndpoints.GetOPNSenseVersion().ToString()));
|
||||
|
||||
// Initialize properties with null values
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("ProductName", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("ProductVersion", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("OsVersion", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Hostname", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("SystemTime", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("Uptime", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("CpuUsage", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("MemoryUsage", null));
|
||||
connectionInfo.Properties.Add(new PSNoteProperty("LastUpdate", null));
|
||||
|
||||
// If not connected, return the object with null values
|
||||
if (!_apiClient.IsConnected)
|
||||
{
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try to get system information
|
||||
var systemInfo = GetSystemInfo();
|
||||
if (systemInfo != null)
|
||||
{
|
||||
connectionInfo.Properties["ProductName"].Value = systemInfo.ProductName;
|
||||
connectionInfo.Properties["ProductVersion"].Value = systemInfo.ProductVersion;
|
||||
connectionInfo.Properties["OsVersion"].Value = systemInfo.OsVersion;
|
||||
connectionInfo.Properties["Hostname"].Value = systemInfo.Hostname;
|
||||
connectionInfo.Properties["SystemTime"].Value = systemInfo.SystemTime;
|
||||
connectionInfo.Properties["Uptime"].Value = systemInfo.Uptime;
|
||||
}
|
||||
|
||||
// Try to get system status
|
||||
var systemStatus = GetSystemStatus();
|
||||
if (systemStatus != null)
|
||||
{
|
||||
connectionInfo.Properties["CpuUsage"].Value = systemStatus.CpuUsage;
|
||||
connectionInfo.Properties["MemoryUsage"].Value = systemStatus.MemoryUsage;
|
||||
}
|
||||
|
||||
// Try to get firmware status
|
||||
var firmwareStatus = GetFirmwareStatus();
|
||||
if (firmwareStatus != null)
|
||||
{
|
||||
connectionInfo.Properties["LastUpdate"].Value = firmwareStatus.LastCheck;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to get detailed firewall information: {ex.Message}");
|
||||
}
|
||||
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private SystemInfo GetSystemInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<ApiVersionResponse>("core/firmware/status");
|
||||
|
||||
return new SystemInfo
|
||||
{
|
||||
ProductName = response.ProductName,
|
||||
ProductVersion = response.ProductVersion,
|
||||
OsVersion = response.OsVersion,
|
||||
Hostname = GetHostname(),
|
||||
SystemTime = GetSystemTime(),
|
||||
Uptime = GetUptime()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to get system information: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHostname()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<HostnameResponse>("core/system/hostname");
|
||||
return response.Hostname;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSystemTime()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<SystemTimeResponse>("core/system/time");
|
||||
return response.Time;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUptime()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<UptimeResponse>("core/system/status");
|
||||
return response.Uptime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SystemStatus GetSystemStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<SystemResourcesResponse>("core/system/status");
|
||||
|
||||
return new SystemStatus
|
||||
{
|
||||
CpuUsage = response.CpuUsage,
|
||||
MemoryUsage = response.MemoryUsage
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to get system status: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private FirmwareStatus GetFirmwareStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _apiClient.Get<FirmwareStatusResponse>("core/firmware/status");
|
||||
|
||||
return new FirmwareStatus
|
||||
{
|
||||
LastCheck = response.LastCheck
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to get firmware status: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class SystemInfo
|
||||
{
|
||||
public string ProductName { get; set; }
|
||||
public string ProductVersion { get; set; }
|
||||
public string OsVersion { get; set; }
|
||||
public string Hostname { get; set; }
|
||||
public string SystemTime { get; set; }
|
||||
public string Uptime { get; set; }
|
||||
}
|
||||
|
||||
private class SystemStatus
|
||||
{
|
||||
public string CpuUsage { get; set; }
|
||||
public string MemoryUsage { get; set; }
|
||||
}
|
||||
|
||||
private class FirmwareStatus
|
||||
{
|
||||
public string LastCheck { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class HostnameResponse
|
||||
{
|
||||
[JsonProperty("hostname")]
|
||||
public string Hostname { get; set; }
|
||||
}
|
||||
|
||||
internal class SystemTimeResponse
|
||||
{
|
||||
[JsonProperty("time")]
|
||||
public string Time { get; set; }
|
||||
}
|
||||
|
||||
internal class UptimeResponse
|
||||
{
|
||||
[JsonProperty("uptime")]
|
||||
public string Uptime { get; set; }
|
||||
}
|
||||
|
||||
internal class SystemResourcesResponse
|
||||
{
|
||||
[JsonProperty("cpu")]
|
||||
public string CpuUsage { get; set; }
|
||||
|
||||
[JsonProperty("memory")]
|
||||
public string MemoryUsage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using PSOPNSenseAPI.Logging;
|
||||
|
||||
namespace PSOPNSenseAPI.Services
|
||||
{
|
||||
@@ -8,6 +9,7 @@ namespace PSOPNSenseAPI.Services
|
||||
public class OPNSenseSessionState
|
||||
{
|
||||
private static readonly Lazy<OPNSenseSessionState> _instance = new Lazy<OPNSenseSessionState>(() => new OPNSenseSessionState());
|
||||
private OPNSenseApiEndpoints _apiEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance
|
||||
@@ -19,11 +21,44 @@ namespace PSOPNSenseAPI.Services
|
||||
/// </summary>
|
||||
public OPNSenseApiClient ApiClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the API endpoints
|
||||
/// </summary>
|
||||
public OPNSenseApiEndpoints ApiEndpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_apiEndpoints == null && ApiClient != null)
|
||||
{
|
||||
_apiEndpoints = new OPNSenseApiEndpoints(ApiClient, new NullLogger());
|
||||
}
|
||||
return _apiEndpoints;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor to prevent instantiation
|
||||
/// </summary>
|
||||
private OPNSenseSessionState()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the API endpoints
|
||||
/// </summary>
|
||||
public void ResetApiEndpoints()
|
||||
{
|
||||
if (ApiClient != null)
|
||||
{
|
||||
_apiEndpoints = new OPNSenseApiEndpoints(ApiClient, new NullLogger());
|
||||
}
|
||||
else
|
||||
{
|
||||
_apiEndpoints = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user