Skip to content

fix: dev to hotfix - #295

Merged
JohnDuprey merged 44 commits into
mainfrom
dev
Aug 14, 2026
Merged

fix: dev to hotfix#295
JohnDuprey merged 44 commits into
mainfrom
dev

Conversation

@JohnDuprey

Copy link
Copy Markdown
Contributor

No description provided.

k-grube and others added 30 commits August 10, 2026 20:28
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
Zacgoose and others added 14 commits August 14, 2026 22:12
…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.
- 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.
@JohnDuprey
JohnDuprey merged commit 674a80d into main Aug 14, 2026
13 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants