fix: dev to hotfix - #295
Merged
Merged
Conversation
Signed-off-by: John Duprey <john@cyberdrain.com>
- Add feed shape validation to skip silently when GitHub Pages returns an error page instead of valid JSON - Build a lookup table from both the Huntress feed and CIPP MaliciousApps.json so matches from either list are fully described - Fall back to the service principal's own display name so alert rows are never anonymous - Rename 'Huntress Added' to 'Listed On' and add a 'Source' field indicating Huntress or CIPP - Add Pester tests covering feed availability guards and per-source match descriptions
Avoid cancelling in-flight queries when the tenant selector is syncing the initial tenant to the URL. This prevented mount fetches from completing since react-query does not retry cancelled queries.
Refactor cache aggregation into a pure `deriveCacheSummary` helper and exclude on-demand cache types from freshness age calculations so tenants are not permanently marked stale by rarely refreshed SharePoint/OneDrive collections. Keep those same types included in scale totals, and add focused tests covering stale/warning/missing classification, ad-hoc-only tenants, and aggregate count behavior.
Explain that the reseller option only creates a billing relationship, not tenant onboarding or admin access, and note that acceptance must be verified in Partner Center.
Replace the truncated five-row AllTenantsRowList with a new AllTenantsCacheList component that scrolls and expands each tenant to show which collections are behind and when they last ran. The deriveCacheSummary hook now attaches stale collections (oldest first) to each tenant row, distinguishes ad-hoc-only tenants from uncached ones, removes the five-row cap, and sorts the list worst-first (never cached → oldest age).
Both SPO DB cache collectors were swallowing all failures, leaving the collection reporting success while the count rows kept stale timestamps. Now: - Empty responses from the admin endpoint throw instead of silently passing through - 401s caused by missing SharePoint consent are caught, logged as Warning, and skipped (it's a standing tenant state, not a transient error) - All other failures rethrow so Invoke-CIPPDBCacheCollection counts them correctly Also adds Pester tests for both collectors and for Get-CIPPSPOTenant covering the SPOAccessDenied flag, rethrow behaviour, and the happy path.
Add a dedicated AnyTenant backend endpoint that reports whether the CIPP host tenant is a Microsoft Partner tenant. The frontend now uses this shared API instead of a direct Graph request so partner-only onboarding and GDAP permission flows stay available for custom roles that cannot access the partner tenant directly.
Add Tenant hides nothing, but Add GDAP Tenant and Get Reseller Invite Link are disabled where the CIPP host tenant is not a Microsoft Partner tenant. Now that ListPartnerTenantInfo makes that determination reliable for custom roles, state the condition on the page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Treat Device Preparation profile assignments as user-group-only, map “all users” to the Intune virtual group target, and block unsupported “all devices” writes. The standard now checks assignment drift separately from settings drift and can repair assignment-only drift in place. Also hardens policy/template deployment by preferring the selected template RowKey over display-name fallback, surfacing corrupt template rows in template listings instead of dropping them, and failing early on empty RAWJson or missing derived policy names. Added and updated Pester coverage for assignment-target logic and DevicePrepProfile assignment remediation behavior.
Prevent PWPush account lookups from failing hard when bearer-auth account retrieval errors or returns no data by returning a user-visible placeholder option instead. Also harden `CippAutocomplete` to treat null `dataKey` payloads and null items as empty results, avoiding null-option rendering and downstream field access errors.
Wrap the SPO ProcessQuery call in a targeted catch so tenant-level SharePoint app-only consent gaps are surfaced as a clear, actionable error. When the admin endpoint returns 401, the function now throws a specific exception (tagged with `SPOAccessDenied`) that distinguishes configuration/access denial from transient request failures and points operators to reset CPV permissions.
Wraps Update-AzDataTableEntity so callers can merge into rows that already exist without creating missing ones. Add-CIPPAzDataTableEntity's UpsertMerge recreates a concurrently deleted row as a stateless shell, which in the audit log claim path becomes a row that can never be parsed or drained and re-enters every processing cycle. Push-AuditLogProcessingBatchV2 already depends on it.
Ensure permissions are always serialized as a JSON array, even when a single permission is returned.
…ions Introduce Repair-CIPPTable to recreate missing Azure tables and refresh the cache, handling concurrent creation scenarios. Add Test-CIPPTableNotFound to identify errors indicating a table does not exist, ensuring robust error handling in entity operations. Include comprehensive Pester tests for both functions to validate their behavior and integration.
Implement retry logic for Add, Get, and Remove operations in case of TableNotFound errors. This includes invalidating the CreateTable cache, recreating the table, and retrying the operation to prevent stale entries from disrupting data operations.
Feat: Update issue templates and workflows
test: add require-context polyfill to jsdom env, add breadcrumb test
Adds SearchDocs and GetDoc as always-advertised gateway tools, so an agent can answer "how does CIPP do X" instead of guessing. Every other tool returns tenant data; these explain the product, which is what a model needs before it knows which data tool to reach for. Leaving them to be discovered via SearchTools would mean already suspecting the docs exist. The GitBook tree ships in the image (~2 MB of markdown) rather than being fetched from docs.cipp.app: there is no GitBook search API, llms-full.txt is capped at 100 of the 427 pages, and a crawl would put outbound internet in the request path. Shipping it also version-matches the docs to the build, and results still carry live docs.cipp.app links. Indexing lives in CIPPSharp for two measured reasons. Tokenising the corpus in PowerShell took 26s against ~2s in .NET, and the index is a host-scoped static, so it builds once per host rather than once per runspace - a PowerShell $script: cache would have rebuilt it for every worker in the pool. Retrieval is BM25 over heading-delimited sections with domain synonyms and fuzzy matching; results deep-link to the heading that matched. Link derivation is pinned against all 427 published URLs. One rule is not guessable from a path: a folder with no README.md is a GitBook grouping folder and is dropped from the URL, so email/resources/management/equipment/edit.md is served at email/management/equipment/edit. Seven pages would otherwise have had confidently wrong links. Pages GitBook does not publish get a GitHub link and no docsUrl rather than a URL that 404s. Also mounts docs/ into the dev containers at the same path the image uses, and un-excludes docs/ from the context-root .dockerignore - that file is what Dockerfile.release resolves to, so without it the release build fails on the new COPY while dev builds fine.
Update `Get-CippDocsRoot` to prefer `CIPPDocsPath` and only accept candidate folders that actually contain markdown, preventing empty `backend/Docs` mountpoints from shadowing real docs and causing zero-result searches. Align dev compose files to mount docs at `/app/Docs` and set `CIPPDocsPath`, add targeted Pester coverage for docs root selection behavior, and refresh the generated OpenAPI spec to reflect related endpoint metadata updates.
When running DB tests with `allTenants`, filter cached tenant partitions against the current managed tenant list before execution. This prevents tests from running on excluded or removed tenants and logs how many stale cached tenants were skipped.
Domain Analyser does not expose a `DMARCRecord` property. Replace it with the actual properties: `DMARCPresent`, `DMARCFullPolicy`, and `DMARCActionPolicy`.
Delete Device sends Quarantine 'false' alongside Delete 'true', so the allow branch matched and returned before Remove-MobileDevice was ever reached. The device was added to ActiveSyncAllowedDeviceIDs instead of being removed, and the action reported success. Evaluate Delete first. Fixes #218
ListRooms mapped isDirSynced from $RoomMailbox, which is only assigned in the single-room branch, so every row in the list branch came back null. The equipment page read row.isDirSynced while ListEquipment returns the raw Get-Mailbox object in PascalCase, so that guard never fired either. Both left destructive actions enabled on directory-synced mailboxes. Also gates equipment Block/Unblock Sign In on AccountDisabled, so the two are no longer offered simultaneously, matching the rooms page. Fixes #219
Rows for built-in EOP rules with no associated policy carry PolicyName = null, and all six row action conditions called startsWith on it. Because handleActionDisabled runs while the action menu renders, one such row took down the whole menu rather than a single action. Hoists a single isMicrosoftManaged helper that tolerates a missing name. It also tests IsBuiltIn, which the list endpoint actually returns, rather than IsBuiltInProtection, which it never emits. Fixes #222
…cies The list endpoint builds each row as an explicit PSCustomObject and neither field was in any of the three passes, so both columns and both flyout fields rendered empty. Carry them through from whichever object the row is built from. Passes 2 and 3 were also missing IsValid, which pass 1 sets; added so the three passes emit a consistent shape. Fixes #223
The column asked for exceptionReadableDate, but both list paths and the cached report re-emit that field as exceptionDate, so nothing under the requested name ever reached the table and the column was always blank. Read the name the API returns. Fixes #224
Check User Access, Recycle Bin and Check Cleanup Job Status are customComponent dialogs but did not set hideBulk, so they were offered as bulk actions. The bulk path renders CippApiDialog unconditionally and has no customComponent branch, so selecting them opened an empty confirmation dialog for an action with no url and no type, and nothing ran. Adds hideBulk to the three, matching Manage Permissions on the same page, and filters customComponent actions out of getBulkActions so a future one cannot fall through by omission. Fixes #225
…y row Both customDataformatters opened by collapsing the selection to row[0] and returned a single object. CippApiDialog only enters bulk request mode when the formatter returns an array, so one request went out and the remaining selected rows were silently skipped while the dialog reported success. Map over the selection in both, as Start Version Cleanup Job already does. Edit Site now evaluates isGroupSite per row, so a mixed selection is no longer judged by the first site's rootWebTemplate. Fixes #226
Config/DocsPublishedPages.txt decides whether a SearchDocs result gets a docs.cipp.app link or falls back to GitHub, and only docs.cipp.app/llms.txt knows which pages are actually live. Until now that list was whatever someone last committed, so a page published after the snapshot stayed unlinkable until a human remembered to re-run the script. A build-docspages stage now refreshes it from the live site and overwrites the committed copy in the image, alongside how openapi.json and function-parameters.json are already generated. The committed file becomes the fallback rather than the source of truth. The fetch must never take the build down with it, so -AllowFallback warns and leaves the committed list in place instead of throwing. That covers both a failed request and a request that succeeds but parses to nothing - a captive portal or an error page answers 200 with a body yielding zero slugs, and overwriting a good list with that would strip the docs link off every result. ARG BUILD_DATE is declared in the stage purely to bust the layer cache. Its only other inputs are the script and the committed list, so without it Docker would reuse an earlier layer and ship a stale list despite the stage existing. The stage is tiny and independent, so unlike the version args near the frontend build it invalidates nothing expensive.
feat: mcp docs search tools
- Remove false-positive throws in OneDrive and SharePoint cached report functions when usage rows are empty — an empty usage set is a valid cached result and the merge is a left join on the site listing anyway. - Add CippSharePointQuotaCard component showing tenant-wide storage usage bar on the SharePoint page. - Add anonymization and empty-usage-report alerts to the SharePoint page table header so users understand why owner/usage columns may be blank.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sites and OneDrive drives with no matching usage report row were showing '0 GB' for storage, which is indistinguishable from a genuinely empty drive. Null is now preserved and passed through so the UI can show the absence of data rather than a misleading zero. Also adds Multi-Geo support to the SharePoint quota card: per-geo usage chips are shown when the tenant spans more than one geo location, and the raw GeoLocations array is now included in the ListSharepointQuota response.
A throw in the delta lookup escaped Push-ExecScheduledCommand with no result written, leaving the task on the orchestrator's Pending claim to be re-picked as stale every hour. Changes: - Wrap delta lookup + refresh in try/catch; record failure on the task and reschedule instead of leaking the exception - Normalise bare-number Recurrence values (days) before the recurrence switch so they stay recurring - Extract New-CIPPTaskDeltaQuery to centralise trigger→delta-params mapping across task creation, rebuild, and repair - Get-DeltaQueryUrl now self-heals a missing DeltaQueries row by rebuilding from the owning task's trigger - New-GraphDeltaQuery promotes Write-Error to throw so callers can catch it - Add DeltaQueries to the backup table list - Add Pester tests for all three changed functions
- Refactor shared GitHub function-app call into a reusable helper - On 401/403/429 from a configured PAT, log a warning and fall back to the anonymous function-app path for GET requests - Serve cached releases on refresh failure instead of 500-ing - Change release notes dialog default selection to baseTag (vX.Y.0) so feature release notes lead; hotfix notes remain accessible via dropdown
Add -NoFallback switch to Invoke-GitHubApiRequest to skip the anonymous function-app fallback. Use it in the extension test endpoint so a rejected PAT returns an error instead of a false success.
KelvinTegelaar
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.