Skip to content
5 changes: 4 additions & 1 deletion Module/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,14 @@ UltraTree uses internal configuration that can be customized by modifying `Priva
|---------|---------|-------------|
| `MaxDuplicateGroups` | 20 | Max duplicate groups in HTML report |
| `MaxPathsPerGroup` | 5 | Max file paths shown per duplicate group |
| `MaxTopFolders` | 8 | Top folders displayed in bar chart |
| `MaxTopFolders` | 25 | Top folders in ranked table per drive |
| `MaxTopFiles` | 50 | Top files in ranked table per drive |
| `MaxFileTypes` | 10 | Top file types to display |
| `MaxResults` | 40 | Max items in results table |
| `MaxPathLength` | 50 | Truncate paths longer than this |

`Get-FolderSizes` returns `Items` as one mixed file/folder list capped by `-Top` (default 40). For HTML reports that need many Top Files rows, pass a larger `-Top` (for example `-Top 200`) to `Get-FolderSizes` before `ConvertTo-NinjaOneHtml`.

### Disk Health Thresholds

| Setting | Default | Description |
Expand Down
17 changes: 15 additions & 2 deletions Module/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- TBD
## [1.0.2]

### Added

- `ConvertTo-NinjaOneHtml`: `-MaxTopFiles`, `-MaxTopFolders`, `-ShowAllResults`, `-FooterSuffix` parameters
- Per-drive ranked **Top Files** and **Top Folders** tables (full width)
- Two-column **Cleanup** + **File Types** layout per drive
- `New-HtmlRankedStack` helper for stacked ranked tables in NinjaOne WYSIWYG

### Changed

- NinjaOne dark mode: `stat-desc` for muted text; explicit dark text on info-card titles/descriptions
- Status badges use inline background and foreground colors (WYSIWYG-safe contrast)
- Footer shows UltraTree version from the module manifest; optional `-FooterSuffix` for caller branding
- Removed duplicate `$script:Config.Version`; version comes from `UltraTree.psd1` only

## [0.2.0]

### Added

- Initial release.

7 changes: 6 additions & 1 deletion Module/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ Control how much data appears in HTML reports:
|---------|---------|-------------|
| `MaxDuplicateGroups` | 20 | Maximum duplicate groups shown in report |
| `MaxPathsPerGroup` | 5 | Maximum file paths shown per duplicate group |
| `MaxTopFolders` | 8 | Number of folders in the bar chart |
| `MaxTopFolders` | 25 | Top folders in ranked table per drive |
| `MaxTopFiles` | 50 | Top files in ranked table per drive |
| `MaxFileTypes` | 10 | Number of file types in the table |
| `MaxResults` | 40 | Maximum items in the full results table |
| `MaxPathLength` | 50 | Truncate paths longer than this in display |
| `MaxLabelLength` | 12 | Truncate chart labels longer than this |

### Top Files / Top Folders and `-Top`

`ConvertTo-NinjaOneHtml` builds per-drive Top Files and Top Folders tables from `Get-FolderSizes` output. `Items` is a **single mixed list** of files and folders, sorted by size and truncated by `-Top` (default 40) across all drives. Folders usually fill most of that list, so raise `-Top` when you need more file rows (for example `-Top 200`).

## Disk Health Thresholds

Control health status indicators:
Expand Down
50 changes: 37 additions & 13 deletions Module/docs/functions/convertto-ninjaonehtml.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,62 @@ Converts scan results from `Get-FolderSizes` into an HTML report optimized for N

```powershell
ConvertTo-NinjaOneHtml [-ScanResults] <PSCustomObject>
[[-MaxTopFiles] <int>]
[[-MaxTopFolders] <int>]
[[-ShowAllResults] <bool>]
[[-FooterSuffix] <string>]
```

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| **ScanResults** | PSCustomObject | Yes | Output from `Get-FolderSizes`. Supports pipeline input. |
| **MaxTopFiles** | int | No | Max file rows per drive in Top Files table. Default: 50 (`Display.MaxTopFiles`). |
| **MaxTopFolders** | int | No | Max folder rows per drive in Top Folders table. Default: 25 (`Display.MaxTopFolders`). |
| **ShowAllResults** | bool | No | Include the full "All Results by Size" table. Default: `$true`. |
| **FooterSuffix** | string | No | Text appended after the UltraTree version in the footer (e.g. `", Script v1.4.2"`). |

## Examples

### Basic Usage

```powershell
$results = Get-FolderSizes -DriveLetter C
$results = Get-FolderSizes -DriveLetter C -Top 200
$html = ConvertTo-NinjaOneHtml -ScanResults $results
```

### Pipeline Usage

```powershell
$html = Get-FolderSizes -AllDrives | ConvertTo-NinjaOneHtml
$html = Get-FolderSizes -AllDrives -Top 200 | ConvertTo-NinjaOneHtml
```

### Full Scan with Duplicates

```powershell
$html = Get-FolderSizes -AllDrives -FindDuplicates | ConvertTo-NinjaOneHtml
$html = Get-FolderSizes -AllDrives -FindDuplicates -Top 200 | ConvertTo-NinjaOneHtml
```

### Compact report (no full results table)

```powershell
$html = Get-FolderSizes -DriveLetter C -Top 200 |
ConvertTo-NinjaOneHtml -ShowAllResults:$false -FooterSuffix ", Script v1.4.2"
```

### Save to File

```powershell
Get-FolderSizes -AllDrives -FindDuplicates |
Get-FolderSizes -AllDrives -FindDuplicates -Top 200 |
ConvertTo-NinjaOneHtml |
Out-File "DiskReport.html" -Encoding UTF8
```

### Set NinjaOne Custom Field

```powershell
$results = Get-FolderSizes -AllDrives -FindDuplicates
$results = Get-FolderSizes -AllDrives -FindDuplicates -Top 200
$html = $results | ConvertTo-NinjaOneHtml
$html | Ninja-Property-Set-Piped treesize
```
Expand All @@ -68,8 +83,9 @@ Each scanned drive gets its own section with:

- **Drive Stats** - Used space, free space, health status (Healthy/Warning/Critical)
- **Disk Usage Chart** - Visual bar showing used vs free space
- **Top Folders Chart** - Bar chart of largest folders
- **File Types Table** - Breakdown by extension
- **Top Files** - Ranked table of largest files (path, size, modified)
- **Top Folders** - Ranked table of largest folders
- **Cleanup** and **File Types** - Side-by-side in a two-column row
- **Cleanup Suggestions** - Categorized cleanup opportunities

### Duplicates Section
Expand All @@ -82,36 +98,44 @@ If `-FindDuplicates` was used:

### Results Table

When `-ShowAllResults` is true (default):

- Full sortable table of all items
- Color-coded by size severity
- Shows path, size, type, and last modified date

### Footer

- Scan timestamp
- UltraTree version
- UltraTree module version (from the loaded module manifest)
- Optional suffix from `-FooterSuffix`

## HTML Features

The generated HTML includes:

- **Bootstrap 5** - Responsive grid and components
- **Font Awesome 6** - Icons for status and categories
- **Charts.css** - Lightweight CSS-based charts
- **Dark/Light support** - Respects system preference
- **Inline badge colors** - Readable in NinjaOne WYSIWYG (wrapper CSS is stripped)
- **Dark/Light support** - Uses `stat-desc` for muted text; explicit dark text on info cards
- **Mobile-friendly** - Responsive design

## Customization

The HTML output is controlled by the module's configuration. See [Configuration](../configuration.md) for options like:

- `MaxTopFolders` - Number of folders in bar chart
- `MaxTopFiles` - Top files in ranked table (default 50)
- `MaxTopFolders` - Top folders in ranked table (default 25)
- `MaxFileTypes` - Number of file types shown
- `MaxResults` - Items in results table
- `MaxDuplicateGroups` - Duplicate groups displayed

### `-Top` interaction

`Get-FolderSizes` returns `Items` as one mixed file/folder list truncated by `-Top` (default 40). Folders usually dominate that list. Use a larger `-Top` (for example `-Top 200`) when you need the Top Files table to fill out — especially with `-AllDrives`.

## Notes

- HTML includes external CDN references for Bootstrap, Font Awesome, and Charts.css
- HTML assumes Bootstrap 5 and Font Awesome 6 are available in the NinjaOne WYSIWYG host page
- For offline viewing, wrap with `New-HtmlWrapper` (loads CDN assets)
- Best viewed in modern browsers or NinjaOne WYSIWYG fields
- For offline viewing, external resources need internet connectivity
49 changes: 28 additions & 21 deletions Module/src/Tests/Unit/Private/HtmlGeneration.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BeforeAll {
BeforeAll {
Set-Location -Path $PSScriptRoot
$ModuleName = 'UltraTree'
$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1")
Expand All @@ -11,7 +11,7 @@ Describe 'HTML Generation Functions' -Tag Unit {
Context 'New-HtmlStatCard' {
It 'Generates valid stat card HTML' {
InModuleScope UltraTree {
$html = New-HtmlStatCard -Value "100 GB" -Description "Test Desc" -Color "#ff0000"
$html = New-HtmlStatCard -Value '100 GB' -Description 'Test Desc' -Color '#ff0000'
$html | Should -Match 'class="stat-card"'
$html | Should -Match 'class="stat-value"'
$html | Should -Match 'class="stat-desc"'
Expand All @@ -23,14 +23,14 @@ Describe 'HTML Generation Functions' -Tag Unit {

It 'Includes icon when provided' {
InModuleScope UltraTree {
$html = New-HtmlStatCard -Value "5" -Description "Items" -Icon "fas fa-folder"
$html = New-HtmlStatCard -Value '5' -Description 'Items' -Icon 'fas fa-folder'
$html | Should -Match 'fas fa-folder'
}
}

It 'Uses default color when not provided' {
InModuleScope UltraTree {
$html = New-HtmlStatCard -Value "5" -Description "Items"
$html = New-HtmlStatCard -Value '5' -Description 'Items'
$html | Should -Match '#337ab7'
}
}
Expand All @@ -39,22 +39,24 @@ Describe 'HTML Generation Functions' -Tag Unit {
Context 'New-HtmlInfoCard' {
It 'Generates info card with Warning type' {
InModuleScope UltraTree {
$html = New-HtmlInfoCard -Title "Test" -Description "Desc" -Type "Warning"
$html = New-HtmlInfoCard -Title 'Test' -Description 'Desc' -Type 'Warning'
$html | Should -Match 'class="info-card warning"'
$html | Should -Match 'fa-solid fa-triangle-exclamation'
$html | Should -Match 'info-title" style="color: #333;"'
$html | Should -Match 'info-description" style="color: #666;"'
}
}

It 'Generates info card with Danger type' {
InModuleScope UltraTree {
$html = New-HtmlInfoCard -Title "Test" -Description "Desc" -Type "Danger"
$html = New-HtmlInfoCard -Title 'Test' -Description 'Desc' -Type 'Danger'
$html | Should -Match 'class="info-card danger"'
}
}

It 'Info type has no extra class' {
InModuleScope UltraTree {
$html = New-HtmlInfoCard -Title "Test" -Description "Desc" -Type "Info"
$html = New-HtmlInfoCard -Title 'Test' -Description 'Desc' -Type 'Info'
$html | Should -Match 'class="info-card"'
$html | Should -Not -Match 'class="info-card info"'
}
Expand All @@ -64,31 +66,36 @@ Describe 'HTML Generation Functions' -Tag Unit {
Context 'New-HtmlTag' {
It 'Generates basic tag' {
InModuleScope UltraTree {
$html = New-HtmlTag -Text "Healthy"
$html = New-HtmlTag -Text 'Healthy'
$html | Should -Match 'class="tag"'
$html | Should -Match 'Healthy'
$html | Should -Match 'color: #333'
}
}

It 'Adds expired type class' {
InModuleScope UltraTree {
$html = New-HtmlTag -Text "Critical" -Type "expired"
$html = New-HtmlTag -Text 'Critical' -Type 'expired'
$html | Should -Match 'class="tag expired"'
$html | Should -Match 'background-color: #d9534f'
$html | Should -Match 'color: #fff'
}
}

It 'Adds disabled type class' {
InModuleScope UltraTree {
$html = New-HtmlTag -Text "Warning" -Type "disabled"
$html = New-HtmlTag -Text 'Warning' -Type 'disabled'
$html | Should -Match 'class="tag disabled"'
$html | Should -Match 'background-color: #f0ad4e'
$html | Should -Match 'color: #333'
}
}
}

Context 'New-HtmlCard' {
It 'Generates card with title and body' {
InModuleScope UltraTree {
$html = New-HtmlCard -Title "Test Card" -Body "<p>Content</p>"
$html = New-HtmlCard -Title 'Test Card' -Body '<p>Content</p>'
$html | Should -Match 'class="card flex-grow-1"'
$html | Should -Match 'class="card-title-box"'
$html | Should -Match 'Test Card'
Expand All @@ -98,35 +105,35 @@ Describe 'HTML Generation Functions' -Tag Unit {

It 'Includes icon when provided' {
InModuleScope UltraTree {
$html = New-HtmlCard -Title "Test" -Icon "fas fa-folder" -Body "Content"
$html = New-HtmlCard -Title 'Test' -Icon 'fas fa-folder' -Body 'Content'
$html | Should -Match 'fas fa-folder'
}
}

It 'Applies body style' {
InModuleScope UltraTree {
$html = New-HtmlCard -Title "Test" -Body "Content" -BodyStyle "padding: 0;"
$html = New-HtmlCard -Title 'Test' -Body 'Content' -BodyStyle 'padding: 0;'
$html | Should -Match 'style="padding: 0;"'
}
}
}

Context 'New-HtmlBarChart' {
It 'Returns empty string for null items' {
InModuleScope UltraTree { New-HtmlBarChart -Items $null | Should -Be "" }
InModuleScope UltraTree { New-HtmlBarChart -Items $null | Should -Be '' }
}

It 'Returns empty string for empty array' {
InModuleScope UltraTree { New-HtmlBarChart -Items @() | Should -Be "" }
InModuleScope UltraTree { New-HtmlBarChart -Items @() | Should -Be '' }
}

It 'Generates chart with items' {
InModuleScope UltraTree {
$items = @(
@{ Label = "Folder1"; Value = 1GB }
@{ Label = "Folder2"; Value = 500MB }
@{ Label = 'Folder1'; Value = 1GB }
@{ Label = 'Folder2'; Value = 500MB }
)
$html = New-HtmlBarChart -Items $items -Title "Test Chart"
$html = New-HtmlBarChart -Items $items -Title 'Test Chart'
$html | Should -Match 'charts-css bar'
$html | Should -Match 'Test Chart'
$html | Should -Match 'Folder1'
Expand All @@ -137,11 +144,11 @@ Describe 'HTML Generation Functions' -Tag Unit {

Context 'New-HtmlDuplicatesTable' {
It 'Returns empty string for null groups' {
InModuleScope UltraTree { New-HtmlDuplicatesTable -DuplicateGroups $null -TotalWasted 0 | Should -Be "" }
InModuleScope UltraTree { New-HtmlDuplicatesTable -DuplicateGroups $null -TotalWasted 0 | Should -Be '' }
}

It 'Returns empty string for empty groups' {
InModuleScope UltraTree { New-HtmlDuplicatesTable -DuplicateGroups @() -TotalWasted 0 | Should -Be "" }
InModuleScope UltraTree { New-HtmlDuplicatesTable -DuplicateGroups @() -TotalWasted 0 | Should -Be '' }
}

It 'Generates table with duplicate groups' {
Expand All @@ -150,7 +157,7 @@ Describe 'HTML Generation Functions' -Tag Unit {
[PSCustomObject]@{
FileSize = 100MB
WastedSpace = 100MB
Files = @("C:\path1\file.exe", "C:\path2\file.exe")
Files = @('C:\path1\file.exe', 'C:\path2\file.exe')
}
)
$html = New-HtmlDuplicatesTable -DuplicateGroups $groups -TotalWasted 100MB
Expand Down
Loading
Loading