-
Notifications
You must be signed in to change notification settings - Fork 366
Addition of non-TrueNAS Managed App visualization & stats #12775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
- Add 'source' field to App TypeScript interface - Source can be 'truenas' or 'external' - Aligns with middleware API schema changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add isExternalApp computed property to app-row and app-info-card - Disable start/stop/restart buttons for external apps - Disable edit/update/convert/rollback/delete buttons for external apps - Show "Manage by deployment tool" tooltip for disabled buttons - Roll Back button now hidden entirely for external apps External apps (deployed via Portainer, Docker CLI, etc.) can only be monitored in TrueNAS. Management operations must be performed through the original deployment tool. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…d apps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…e usage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
… Apps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Hide Workloads, Notes, and Metadata tiles for external apps since they are not managed by TrueNAS and these details are not relevant. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…f version info For external apps in Application Info tile: - Replace App Version and Version fields with "Deployment: Deployed via External Tool" - Hide Source and Train fields (not relevant for external apps) - Keep Name field visible 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Hide checkbox in app-row for external apps - Filter external apps from checkedApps, activeCheckedApps, and stoppedCheckedApps - Update allAppsChecked to only consider TrueNAS apps - Update toggleAppsChecked to only select TrueNAS apps - Prevents bulk actions (start/stop/update/delete) on external apps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Claude finished @alexandrabain's task —— View job Code Review Complete! 🎉Great work on this feature addition! The implementation is solid overall with good test coverage and modern Angular patterns. Here's my feedback: ✅ StrengthsThe PR demonstrates strong architectural decisions with signal-based reactive patterns, comprehensive test coverage (89.22%), and well-structured utility functions that handle edge cases properly. 🔴 Critical Issue: Memory Leaksrc/app/pages/apps/components/installed-apps/installed-apps-list/installed-apps-list.component.ts:617-619 There's a memory leak in the readonly totalUtilization$ = this.installedAppsStore.installedApps$.pipe(
// ... transformations ...
takeUntilDestroyed(this.destroyRef),
shareReplay({ bufferSize: 1, refCount: true }), // ❌ WRONG ORDER
);Problem: When Solution: Place readonly totalUtilization$ = this.installedAppsStore.installedApps$.pipe(
// ... transformations ...
takeUntilDestroyed(this.destroyRef), // ✅ First
shareReplay({ bufferSize: 1, refCount: true }), // ✅ Second
);Why this matters: When Note: I can see from your commit history (141c2d1) that you've already identified and attempted to fix this - make sure the correct order is in the final version! 🔍
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #12775 +/- ##
==========================================
+ Coverage 86.35% 86.38% +0.02%
==========================================
Files 1832 1834 +2
Lines 68493 68574 +81
Branches 8392 8419 +27
==========================================
+ Hits 59149 59236 +87
+ Misses 9344 9338 -6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Add null safety checks (app.source ?? 'truenas') for backward compatibility - Replace manual Observable with of() in totalUtilizationAnalysis getter - Ensures external apps filtering works with existing apps that may not have source field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add source field to mock app data - Mock AppsStatsService.getStatsForApp() to return valid observable - Prevents "undefined where stream was expected" error in combineLatest 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add optional chaining for app.source in isExternalApp computed - Add source field to app-row test mock data - Ensures backward compatibility with apps that don't have source field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add source field to mock app data - Mock AppsStatsService.getStatsForApp() to return valid observable - Prevents combineLatest error in tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add null safety checks to incomingTrafficBits and outgoingTrafficBits computed signals - Add keyboard navigation (Enter/Space) to collapsible section headers - Add tabindex="0" for keyboard accessibility on section headers - Prevents runtime errors when stats are null/undefined (e.g., stopped apps) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Fix stats aggregation performance: Convert getter to cached observable with shareReplay - Add ARIA attributes (role, aria-expanded, aria-controls) for screen reader support - Add helper methods (isExternalApp, isTruenasApp) for consistent app type checking - Add defensive null checks to network stats (rx_bytes, tx_bytes) - Improves maintainability and eliminates duplicate null-handling logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add external app (external-nginx) to test data - Test filtering of TrueNAS vs external apps - Test bulk selection excludes external apps - Test active/stopped checked apps exclude external apps - Update bulk update test to verify only TrueNAS apps included - Improves test coverage for the main feature of this PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ation - Add optional chaining (stats?.cpu_usage, stats?.memory) for top-level properties - Add optional chaining (net?.rx_bytes, net?.tx_bytes) for network stats - Prevents potential runtime errors with null/undefined stats from external apps - All stats properties now gracefully handle missing data 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…leanup - Replace @UntilDestroy() decorator with DestroyRef inject pattern - Replace untilDestroyed(this) with takeUntilDestroyed(this.destroyRef) - Update installed-apps-list.component.ts - Update app-info-card.component.ts - Follows modern Angular standards per CLAUDE.md guidelines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ggregation - Add safeAdd() helper to prevent NaN from null/undefined/non-number values - Add safeNetworkSum() helper for safe network stats accumulation - Improve empty check: !apps?.length || !apps.some((app) => !!app) - Add typeof stats !== 'object' check to validate stats shape - All math operations now guaranteed to return valid numbers, never NaN - Handles all edge cases: null apps, undefined stats, missing properties, empty arrays 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Move $event.preventDefault() before signal update - Ensures preventDefault() is called even if signal update throws error - Prevents unwanted page scroll on space key press - Applied to both TrueNAS Apps and Other Apps section headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…nal app detection utility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…stants - Update App interface to use 'TRUENAS' | 'EXTERNAL' instead of lowercase - Fix isExternalApp utility to check for 'EXTERNAL' instead of 'external' - Update all component comparisons to use uppercase constants - Update all test mocks and assertions to use uppercase values - Ensures external app detection works correctly with middleware API
Move debounceTime(300) before switchMap to debounce app list changes rather than the result stream. This prevents excessive recalculations when apps are added/removed rapidly while avoiding the test failures that occurred when debouncing was inside the switchMap.
Change stats from input.required<AppStats>() to input<AppStats | null>() to accurately reflect that the async pipe can emit null before the observable emits its first value. This matches the defensive coding already in place with optional chaining.
Document that dataSource is sorted as a whole, but template filters preserve sort order within separate TrueNAS and External sections. This improves maintainability for future developers.
Converted 4 failing tests to use fakeAsync() and tick() to properly handle timing with debounceTime(300) and shareReplay caching. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Changed mock from of(apps) to BehaviorSubject<App[]> to allow tests to emit new values after setting up test-specific mocks. This properly triggers the debounced observable chain and clears the shareReplay cache. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1. Critical: Fixed memory leak in totalUtilization$ observable - Moved takeUntilDestroyed from inner combineLatest to outer chain - Prevents accumulation of subscriptions on each installedApps$ emission 2. Moderate: Prevent external apps from being selected - Added defensive check in toggleAppChecked() to block selection - Ensures selection model never contains external app IDs 3. Moderate: Improved JSDoc clarity for setDatasourceWithSort - Added sorting strategy explanation - Documented why apps are sorted together - Added concrete example for template rendering behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The takeUntilDestroyed operator is redundant because:
- shareReplay({ refCount: true }) automatically cleans up when no subscribers remain
- async pipe in template automatically unsubscribes on component destruction
- Having both could interfere with shareReplay's refCount behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…eplay Correct operator placement for proper cleanup: - takeUntilDestroyed BEFORE shareReplay ensures source subscription cleanup when component is destroyed - shareReplay after it can still share among multiple template subscriptions - refCount: true handles cleanup when all subscribers leave Previous placement after shareReplay only affected downstream subscribers, not the source subscription, risking accumulation on component recreation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The tests were failing because firstValueFrom was subscribing AFTER the observable had already processed emissions with takeUntilDestroyed. Fixed by: - Subscribing directly before triggering installedApps$.next() - Using synchronous subscription pattern in fakeAsync tests - Properly unsubscribing after test assertions This ensures the subscription is active when values are emitted through the debounced observable chain. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1. Observable cleanup clarification
- Removed takeUntilDestroyed from totalUtilization$ observable
- Added comment explaining shareReplay({ refCount: true }) handles cleanup
- Prevents future confusion about redundant cleanup operators
2. Performance: Convert getters to computed signals
- Changed filteredTruenasApps and filteredExternalApps to computed()
- Eliminates redundant filtering on every change detection cycle
- Updated all usages in component, template, and tests
3. Data transformation consistency
- Renamed networkRx/networkTx to networkRxBits/networkTxBits
- Moved byte-to-bit conversion (* 8) into component logic
- Removed multiplication from template for consistency with app-row
- Updated test expectations (4500 bytes → 36000 bits)
4. Defensive programming documentation
- Added comment to toggleAppChecked() explaining external app check
- Clarifies this prevents selection if called programmatically
5. Test simplification
- Converted 4 tests from manual subscription to await firstValueFrom()
- Cleaner and more readable test patterns
- Works correctly now that takeUntilDestroyed is removed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add accessibility attributes (role, aria-live) to utilization summary - Consolidate network traffic calculations into memoized computed signal - Fix safeAdd type signature to accept null/undefined for both parameters - Create SCSS variable for grid columns to eliminate duplication - Improve i18n keys for section headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add takeUntilDestroyed before shareReplay to ensure proper cleanup when component is destroyed. While shareReplay with refCount handles inner subscriptions, takeUntilDestroyed ensures the entire chain is torn down on component destruction, following CLAUDE.md guidelines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add missing 'computed' import from @angular/core - Add explicit App type annotations to lambda parameters in: - isSelectedAppVisible getter - filteredTruenasApps computed - filteredExternalApps computed - toggleAppsChecked method Fixes TS7006 and TS2304 errors preventing test compilation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…date Add spectator.detectChanges() in beforeEach after setting dataSource to ensure computed signals (filteredApps, filteredTruenasApps) are re-evaluated and app rows render in the DOM. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Force filteredApps computed signal to recalculate by toggling searchQuery signal. Since dataSource is a plain property (not a signal), the computed only recalculates when its signal dependencies change. Toggling searchQuery causes the computed to re-evaluate and read the updated dataSource value. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…nt initialization
…ild signal issues
- Refactored InstalledAppsListComponent to use modern signal-based architecture matching patterns in containers/enclosure/storage components - Removed non-reactive dataSource property in favor of reactive signals - Created sortedApps computed signal for proper reactive sorting - Fixed test failures caused by non-reactive state management - Extracted network stats calculation to shared utility (network-stats.utils.ts) - Added error handling to totalUtilization$ observable with catchError - Cleaned up redundant type guard check (isTruenasApp already performs null check) - All tests passing (5/5), all lint rules pass
… architecture Convert all remaining getters to computed signals for consistent reactivity: - checkedApps, activeCheckedApps, stoppedCheckedApps - allAppsChecked, hasCheckedApps, hasUpdates - selectedApp converted to signal - isSelectedAppVisible now reactive Add selection change tracking: - Implement selectionChanged signal to trigger reactive updates - Add toggleAppSelection method to track selection state changes - Ensure all selection-dependent UI updates automatically Performance optimizations: - Add appsById computed Map for O(1) app lookups - Reduce checkedApps complexity from O(n*m) to O(n) - Automatic memoization via computed signals Extract network utilities: - Export safeAdd helper function - Add sumNetworkFieldAsBits utility for cleaner aggregations - Remove duplicate safeAdd implementation Accessibility improvements: - Add aria-live="polite" to collapsible section headers - Standardize terminology to "External Apps" throughout Update tests: - Modify tests to use signal-based API with () calls - Use toggleAppSelection instead of direct selection manipulation - Reset installedApps$ in beforeEach to prevent test pollution - All 26 tests passing This refactoring provides: - Consistent reactive architecture throughout component - Automatic change detection with OnPush strategy - Better performance with large app lists - Improved maintainability and predictable data flow
- Updated InstalledAppsComponent to use computed signals that properly call child component signals - Added missing 'computed' import from @angular/core - Updated template to call all signals with parentheses () - Fixed selectedApp, appsUpdateAvailable, hasUpdates, and isSelectedAppVisible to be computed signals - Removed unused App interface import - All tests passing (5/5 parent, 26/26 child) - Lint passing
- Moved shareReplay before takeUntilDestroyed in totalUtilization$ observable - This ensures proper cleanup when component is destroyed - Previous order (takeUntilDestroyed before shareReplay) could cause the shared observable to not clean up properly - Correct order: transformations → shareReplay → takeUntilDestroyed - All tests passing (26/26) - Lint passing
- Moved takeUntilDestroyed BEFORE shareReplay (not after) - This ensures the source subscription is cleaned up when component is destroyed - takeUntilDestroyed after shareReplay only affects subscribers, not the source - Correct order: transformations → takeUntilDestroyed → shareReplay - This corrects the previous commit which had the operators in wrong order - All tests passing (26/26) - Lint passing
…play - Moved takeUntilDestroyed to the end of the pipe (after shareReplay) - This ensures proper cleanup when component is destroyed - takeUntilDestroyed must be last to unsubscribe from shareReplay - shareReplay's refCount:true then triggers disconnection from source - Correct order: transformations → shareReplay → takeUntilDestroyed - All tests passing (26/26) - Lint passing
- Moved takeUntilDestroyed to come BEFORE shareReplay (not after) - When takeUntilDestroyed is AFTER shareReplay, template subscribers (| async) subscribe downstream of takeUntilDestroyed, so it can't clean up upstream sources - When takeUntilDestroyed is BEFORE shareReplay, it properly completes the source stream when component is destroyed, stopping the combineLatest polling - Correct order: transformations → takeUntilDestroyed → shareReplay - This fixes the memory leak where app stats continued polling after component destruction - All tests passing (26/26) - Lint passing
Changes:
[Note dependent on https://github.com/truenas/middleware/pull/17514]
Testing:
Tested new and upgraded deployments, with a variety of apps in both categories.
Tested existing controls for TrueNAS Managed apps remain functional
Tested sorting of application names worked in each group
Tested filtering works across each group.
Downstream