Skip to content

Create, list, delete Android certificates from the UI - #37314

Merged
jacobshandling merged 32 commits into
mainfrom
36689-android-certs-os-settings-ui
Dec 16, 2025
Merged

Create, list, delete Android certificates from the UI#37314
jacobshandling merged 32 commits into
mainfrom
36689-android-certs-os-settings-ui

Conversation

@jacobshandling

@jacobshandling jacobshandling commented Dec 16, 2025

Copy link
Copy Markdown
Contributor

Related issue: Resolves #36689

Screenshot 2025-12-15 at 5 08 02 PM Screenshot 2025-12-15 at 5 09 18 PM Screenshot 2025-12-15 at 5 09 22 PM

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/
  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive Android certificate management in OS Settings with create, list, and delete operations
    • Integrated certificate management with premium tier gating and MDM enablement checks
    • Supports team-scoped certificates with pagination controls
    • Includes validation for certificate names and certificate authority selection
  • Refactor

    • Generalized heading component to support multiple entity types beyond configuration profiles

✏️ Tip: You can customize this high-level summary in your review settings.

@jacobshandling

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Dec 16, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov

codecov Bot commented Dec 16, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.22124% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.96%. Comparing base (2765cde) to head (62aca5f).
⚠️ Report is 42 commits behind head on main.

Files with missing lines Patch % Lines
...vityFeed/GlobalActivityItem/GlobalActivityItem.tsx 0.00% 18 Missing ⚠️
frontend/services/entities/certificates.ts 41.66% 7 Missing ⚠️
...onents/AddCertificateModal/AddCertificateModal.tsx 95.34% 2 Missing ⚠️
...ificates/components/AddCertificateModal/helpers.ts 97.36% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #37314    +/-   ##
========================================
  Coverage   65.95%   65.96%            
========================================
  Files        2336     2344     +8     
  Lines      185738   185870   +132     
  Branches     7854     7883    +29     
========================================
+ Hits       122509   122608    +99     
- Misses      52036    52069    +33     
  Partials    11193    11193            
Flag Coverage Δ
frontend 54.46% <75.22%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Dec 16, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

A new UI feature for managing Android certificates within OS Settings is introduced, including create, list, and delete operations. The implementation adds type definitions for host certificates, creates dedicated UI components (Certificates card, Add/Delete modals), refactors ProfileListHeading into a generic UploadListHeading component, updates navigation and routing, and extends API services with certificate endpoints.

Changes

Cohort / File(s) Summary
Mock Data Updates
frontend/__mocks__/certificatesMock.ts, frontend/__mocks__/hostMock.ts, frontend/__mocks__/mdmMock.ts
Added Android certificate mock data and empty certificates arrays to host and MDM mock objects.
Type Definitions
frontend/interfaces/host.ts
Introduced HostCertStatus union type and IHostCert interface; extended IOSSettings with certificates field.
Navigation Setup
frontend/pages/ManageControlsPage/OSSettings/OSSettingsNavItems.tsx
Added Certificates card to OS Settings navigation items.
Certificates Feature — Main Component
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/_styles.scss
Created Certificates component with react-query pagination, premium tier gating, MDM enablement checks, and integration of add/delete modals. Includes styling for container and MDM disabled state.
Certificates Feature — Add Certificate
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateCard/AddCertificateCard.tsx, _styles.scss
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx, AddCertificateModal.tests.tsx, _styles.scss, helpers.ts, index.ts
Implemented AddCertificateCard trigger component, AddCertificateModal form with validation helpers (name format, uniqueness, length), CA fetching, and certificate creation workflow. Includes comprehensive test coverage.
Certificates Feature — Delete Certificate
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/DeleteCertificateModal.tsx, _styles.scss, index.ts
Implemented DeleteCertificateModal confirmation dialog with delete API call and notification handling.
Certificates Feature — Barrel Export
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/index.ts
Added barrel export for main Certificates component.
UploadListHeading Refactoring
frontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsx, _styles.scss, index.ts
Renamed ProfileListHeading to UploadListHeading and generalized props (onClickAddProfile → onClickAdd; static text → dynamic entityName and createEntityText). Updated class selectors in styles and added index barrel export.
CustomSettings Integration
frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts
Updated CustomSettings to use new UploadListHeading component with dynamic props; removed ProfileListHeading barrel export.
Admin Navigation Update
frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx
frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx
Renamed Certificate authorities navigation label and URL section; updated SettingsSection title.
Routing
frontend/router/paths.ts
Added CONTROLS\_CERTIFICATES path and updated ADMIN\_INTEGRATIONS\_CERTIFICATE\_AUTHORITIES path.
API Services
frontend/services/entities/certificates.ts
Introduced certificate endpoints (getCerts, createCert, deleteCert) with pagination, query typing (IGetCertsParams, IQueryKeyGetCerts), response models (ICertificate, IGetCertsResponse), and request payload (ICreateCert).
Utilities & Cleanup
frontend/services/entities/mdm.ts
frontend/utilities/endpoints.ts
Removed unused mock imports in mdm.ts; added CERTIFICATES endpoint.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Certificates Card
    participant Modal as Add Certificate Modal
    participant API as Certificate API
    participant Notify as Notification

    User->>UI: Click Add Certificate
    UI->>Modal: Open modal
    Modal->>Modal: Fetch certificate authorities
    User->>Modal: Enter certificate details
    Modal->>Modal: Validate form (name, CA, subject)
    User->>Modal: Click Create
    Modal->>API: POST certificate
    API-->>Modal: Success/Error
    alt Success
        Modal->>Notify: Show success toast
        Notify-->>User: Confirmation
        Modal->>UI: Close & refresh list
    else Error
        Modal->>Notify: Show error toast
        Notify-->>User: Error message
    end
Loading
sequenceDiagram
    participant User
    participant UI as Certificates Card
    participant Modal as Delete Certificate Modal
    participant API as Certificate API
    participant Notify as Notification

    User->>UI: Click delete on certificate
    UI->>Modal: Open confirmation modal
    User->>Modal: Click Delete
    Modal->>API: DELETE /certificates/:id
    API-->>Modal: Success/Error
    alt Success
        Modal->>Notify: Show success flash
        Notify-->>User: Confirmation
        Modal->>UI: Close & refresh list
    else Error
        Modal->>Notify: Show error flash
        Notify-->>User: Error message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Validation logic complexity: The AddCertificateModal helpers.ts file contains multi-field validation logic with extensible patterns that warrants careful review of validation rules and message handling.
  • Component composition: Multiple modal and card components with state management (loading, visibility) and integration with NotificationContext.
  • Refactoring scope: UploadListHeading generalization affects CustomSettings and requires verification that the prop changes are correctly propagated.
  • API integration: New certificate CRUD operations with pagination require verification of request/response typing and error handling consistency.
  • Test coverage: AddCertificateModal test suite should be reviewed for edge cases and mocking correctness.

Suggested reviewers

  • getvictor
  • ghernandez345
  • sgress454

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main feature added: creating, listing, and deleting Android certificates from the UI.
Linked Issues check ✅ Passed The PR implements the requirements from issue #36689 to create a UI for managing Android certificates in Controls → OS settings, including create, list, and delete functionality.
Out of Scope Changes check ✅ Passed Changes include refactoring UploadListHeading to UploadListComponent for reuse and updating Certificate Authorities naming in admin section, which are reasonable supporting changes for the main objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed PR description includes related issue, relevant checkboxes completed (changes file, tests, QA), and supporting screenshots, but lacks detail in other template sections.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 36689-android-certs-os-settings-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/_styles.scss (1)

1-2: Consider removing the empty style rule or adding necessary styles.

The .delete-certificate-modal selector contains no style declarations. If no custom styling is needed for this modal, consider removing this file. Otherwise, add the required styles.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/DeleteCertificateModal.tsx (1)

38-41: Consider clarifying the team context in the modal message.

The message states "from all hosts assigned to this team," but the modal doesn't receive or display team information. This could be confusing if:

  1. The feature works at the global level (no teams)
  2. The team context is not clear to the user

Consider one of these approaches:

  1. If team ID is relevant, pass it as a prop and display the team name
  2. If this always operates at the team level, ensure the parent context makes this clear
  3. If teams are optional, adjust the message to handle both scenarios

Example for approach 3:

        <p>
-          This action will remove the <b>{name}</b> certificate from all hosts
-          assigned to this team.
+          This action will remove the <b>{name}</b> certificate from all assigned hosts.
        </p>
frontend/services/entities/certificates.ts (2)

109-122: Misleading variable name and inconsistent query string handling.

Line 114 assigns CERTIFICATES to CERT_TEMPLATES, which is confusing since this endpoint manages certificates, not certificate templates. Additionally, line 116 uses buildQueryStringFromParams correctly, but this pattern is not followed in createCert (line 132).

Apply this diff to improve clarity and consistency:

-  getCerts: ({
-    team_id,
-    page,
-    per_page,
-  }: IGetCertsParams): Promise<IGetCertsResponse> => {
-    const { CERTIFICATES: CERT_TEMPLATES } = endpoints;
-
-    const queryString = buildQueryStringFromParams({ team_id, page, per_page });
-
-    return sendRequest(
-      "GET",
-      queryString ? CERT_TEMPLATES.concat(`?${queryString}`) : CERT_TEMPLATES
-    );
-  },
+  getCerts: ({
+    team_id,
+    page,
+    per_page,
+  }: IGetCertsParams): Promise<IGetCertsResponse> => {
+    const { CERTIFICATES } = endpoints;
+
+    const queryString = buildQueryStringFromParams({ team_id, page, per_page });
+
+    return sendRequest(
+      "GET",
+      queryString ? CERTIFICATES.concat(`?${queryString}`) : CERTIFICATES
+    );
+  },

123-138: Use buildQueryStringFromParams for consistency and consider endpoint helper pattern.

Line 132 manually concatenates the team_id query parameter, while getCerts uses buildQueryStringFromParams. Line 137 manually builds the delete path with template literals.

For consistency, consider using buildQueryStringFromParams in createCert:

  createCert: ({ name, certAuthorityId, subjectName, teamId }: ICreateCert) => {
    const { CERTIFICATES } = endpoints;
    const requestBody = {
      name,
      certificate_authority_id: certAuthorityId,
      subject_name: subjectName,
    };
+   const queryString = buildQueryStringFromParams({ team_id: teamId });
    return sendRequest(
      "POST",
-     teamId ? CERTIFICATES.concat(`?team_id=${teamId}`) : CERTIFICATES,
+     queryString ? CERTIFICATES.concat(`?${queryString}`) : CERTIFICATES,
      requestBody
    );
  },

Additionally, for the deleteCert endpoint, consider following the pattern used elsewhere in this file (e.g., CERTIFICATE_AUTHORITY(id) on line 82) by adding a CERTIFICATE(id) helper in endpoints.ts:

  deleteCert: (id: number) => {
-   return sendRequest("DELETE", endpoints.CERTIFICATES.concat(`/${id}`));
+   return sendRequest("DELETE", endpoints.CERTIFICATE(id));
  },
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx (2)

11-14: Address @ts-ignore suppressions.

The @ts-ignore directives suppress type checking for InputField and Dropdown. This technical debt should be tracked and resolved to maintain type safety.

Consider either:

  1. Adding proper TypeScript definitions for these components, or
  2. Tracking this as a TODO with a linked issue to address the missing types

If these components are widely used without types, consider creating a shared effort to add type definitions across the codebase.


67-80: Consider a more specific query key for Certificate Authorities.

The query key is a simple string "certAuthorities", which may cause cache collision if other components fetch CA data with different parameters or contexts. While this works for the current use case, a more structured key would improve cache predictability.

Consider using a structured query key:

  const {
    data: cAResp,
    isLoading: isLoadingCAs,
    isError: isErrorCAs,
  } = useQuery(
-   "certAuthorities",
+   ["certAuthorities", { scope: "dropdown" }],
    () => {
      return certificatesAPI.getCertificateAuthoritiesList();
    },
    {
      ...DEFAULT_USE_QUERY_OPTIONS,
      select: (data) => data.certificate_authorities,
    }
  );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d265d60 and 6fc59f8.

📒 Files selected for processing (31)
  • changes/36689-update-android-certs-from-ui (1 hunks)
  • frontend/__mocks__/certificatesMock.ts (2 hunks)
  • frontend/__mocks__/hostMock.ts (1 hunks)
  • frontend/__mocks__/mdmMock.ts (1 hunks)
  • frontend/interfaces/host.ts (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/OSSettingsNavItems.tsx (2 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/_styles.scss (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateCard/AddCertificateCard.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateCard/_styles.scss (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tests.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/_styles.scss (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/helpers.ts (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/index.ts (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/DeleteCertificateModal.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/_styles.scss (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/index.ts (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/index.ts (1 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx (2 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts (0 hunks)
  • frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx (1 hunks)
  • frontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsx (3 hunks)
  • frontend/pages/ManageControlsPage/components/UploadListHeading/_styles.scss (1 hunks)
  • frontend/pages/ManageControlsPage/components/UploadListHeading/index.ts (1 hunks)
  • frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx (1 hunks)
  • frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx (1 hunks)
  • frontend/router/paths.ts (2 hunks)
  • frontend/services/entities/certificates.ts (4 hunks)
  • frontend/services/entities/mdm.ts (0 hunks)
  • frontend/utilities/endpoints.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts
  • frontend/services/entities/mdm.ts
🧰 Additional context used
🧬 Code graph analysis (6)
frontend/interfaces/host.ts (1)
server/fleet/hosts.go (1)
  • DiskEncryptionStatus (598-598)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/helpers.ts (2)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx (1)
  • IAddCertFormData (30-34)
frontend/services/entities/certificates.ts (1)
  • ICertificate (55-61)
frontend/__mocks__/certificatesMock.ts (1)
frontend/services/entities/certificates.ts (1)
  • ICertificate (55-61)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/DeleteCertificateModal.tsx (1)
frontend/services/entities/certificates.ts (1)
  • ICertificate (55-61)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx (3)
frontend/services/entities/certificates.ts (1)
  • ICertificate (55-61)
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/helpers.ts (3)
  • generateFormValidations (31-95)
  • IAddCertFormValidation (4-9)
  • validateFormData (107-135)
frontend/utilities/constants.tsx (1)
  • DEFAULT_USE_QUERY_OPTIONS (451-461)
frontend/services/entities/certificates.ts (2)
frontend/services/entities/common.ts (2)
  • PaginationParams (14-17)
  • ListEntitiesResponsePaginationCommon (2-5)
frontend/utilities/url/index.ts (1)
  • buildQueryStringFromParams (101-113)
🔇 Additional comments (30)
frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx (1)

2-2: LGTM!

The blank line improves import organization by separating the React import from third-party libraries.

frontend/__mocks__/mdmMock.ts (1)

64-64: LGTM!

The addition of certificates: [] to the mock data structure correctly aligns with the new certificate management feature and the IHostCert[] type definition.

frontend/interfaces/host.ts (2)

100-116: LGTM!

The new type definitions for host certificates are well-structured and align with the certificate management feature. The HostCertStatus union type appropriately covers all certificate states, and the comment clarifying UI display behavior for pending states is helpful.


123-123: LGTM!

The addition of certificates: IHostCert[] to the IOSSettings interface correctly extends the OS settings structure to support certificate data.

frontend/__mocks__/hostMock.ts (1)

79-79: LGTM!

The addition of certificates: [] to the host mock correctly aligns with the new IOSSettings.certificates field and matches the corresponding change in mdmMock.ts.

changes/36689-update-android-certs-from-ui (1)

1-1: LGTM!

The changelog entry clearly and concisely describes the implemented feature.

frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx (1)

141-141: LGTM!

The title change from "Certificates" to "Certificate authorities" improves clarity and helps distinguish this section from the new Android certificates feature introduced in this PR.

frontend/utilities/endpoints.ts (1)

313-313: No action needed. Verification confirms there is only one CERTIFICATES endpoint declaration at line 313. No duplicate exists.

Likely an incorrect or invalid review comment.

frontend/pages/ManageControlsPage/components/UploadListHeading/_styles.scss (1)

1-18: The UploadListHeading component correctly uses all updated class names.

All CSS class names have been properly updated in the component. The component uses baseClass = "upload-list-heading" and references all nested classes (__upload-name-heading, __actions-heading, __add-button) correctly. No old profile-list-heading references remain.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/index.ts (1)

1-1: LGTM!

The barrel export follows the established pattern used consistently across the codebase.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/_styles.scss (1)

1-14: LGTM!

The styling is well-structured. The monospace font for the textarea is particularly appropriate for certificate content (PEM format).

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/_styles.scss (1)

1-10: LGTM!

The flexbox layout with gap is clean and modern. The fixed height appears consistent with other OS Settings cards.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/index.ts (1)

1-1: LGTM!

The barrel export follows the established pattern.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateCard/_styles.scss (1)

1-20: LGTM!

The styling effectively centers the content and follows BEM naming conventions. The use of flexbox for centering is clean and appropriate.

frontend/pages/ManageControlsPage/OSSettings/OSSettingsNavItems.tsx (1)

8-8: No action required. The Certificates component correctly defines its props as extending IOSSettingsCommonProps with an optional currentPage property, confirming full compatibility with the navigation integration in OSSettingsNavItems.tsx.

frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx (1)

47-48: Path definition is correctly aligned with the new URL structure.

The rename from "Certificates" to "Certificate authorities" is fully implemented and intentional. The ADMIN_INTEGRATIONS_CERTIFICATE_AUTHORITIES path constant in frontend/router/paths.ts (line 62) is correctly set to ${INTEGRATIONS_PREFIX}/certificate-authorities, matching the urlSection value in IntegrationNavItems.tsx. All components—title, urlSection, path constant, and Card component—are properly aligned.

frontend/pages/ManageControlsPage/components/UploadListHeading/index.ts (1)

1-1: Refactor from ProfileListHeading to UploadListHeading is complete.

The barrel export is correct. All references have been properly updated—no remaining ProfileListHeading references exist, and UploadListHeading is correctly imported and used in Certificates.tsx and CustomSettings.tsx.

frontend/__mocks__/certificatesMock.ts (1)

68-80: LGTM!

The Android certificate mock follows the established pattern in this file and correctly implements the ICertificate interface. The factory function provides good flexibility for test customization.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/index.ts (1)

1-1: LGTM!

Standard barrel export pattern for cleaner imports.

frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx (1)

29-29: LGTM!

The refactoring to use the shared UploadListHeading component is well-executed. The new props (entityName, createEntityText, onClickAdd) make the component more flexible and reusable.

Also applies to: 175-179

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tests.tsx (1)

51-332: Excellent test coverage!

The test suite comprehensively covers:

  • Modal rendering with all form fields
  • Validation for empty, invalid, duplicate, and too-long names
  • Required field validation for CA and subject name
  • Full happy path flow
  • Cancel functionality

The tests are well-structured and use appropriate testing patterns with MSW for API mocking.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/DeleteCertificateModal/DeleteCertificateModal.tsx (1)

22-33: LGTM!

The delete handler properly manages loading states and provides clear user feedback through flash messages.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateCard/AddCertificateCard.tsx (1)

13-35: LGTM!

The component is clean and follows established patterns. The GitOps mode integration ensures the Add button is properly disabled when GitOps is enabled.

frontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsx (2)

10-20: Excellent refactoring for reusability!

The updated props interface makes this component generic and reusable across different contexts. The new prop names (entityName, createEntityText, onClickAdd) are more descriptive and self-documenting than the previous onClickAddProfile.


8-8: LGTM!

The baseClass and related CSS class references are consistently updated to match the new component name.

Also applies to: 23-23

frontend/router/paths.ts (1)

62-62: The path rename is safe and complete—all references have been properly updated.

No hardcoded references to the old /integrations/certificates path remain in the codebase. The new constant ADMIN_INTEGRATIONS_CERTIFICATE_AUTHORITIES is actively used in navigation (IntegrationNavItems.tsx) and the certificate modal (AddCertificateModal.tsx), with all consumers importing from the paths definition. This is a clean migration with no breaking changes.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/AddCertificateModal.tsx (1)

100-119: Form submission logic looks correct.

The form validates before enabling submission (line 182), ensuring certAuthorityId is non-empty before parseInt is called (line 107). Error handling provides user feedback, and the success flow properly triggers callbacks.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/components/AddCertificateModal/helpers.ts (2)

31-95: Well-structured validation framework.

The validation logic correctly implements:

  • Sequential validation with early exit on first failure
  • Case-insensitive uniqueness checking (line 56)
  • Appropriate character restrictions for certificate names
  • Clear error messages for each validation rule

The ordering of validations ensures users see the most relevant error first (required → character validation → uniqueness → length).


107-135: Validation execution logic is sound.

The function correctly aggregates field-level validation results and maintains an overall isValid flag. The use of Object.keys with type assertion (line 116) safely iterates over the validation configuration.

frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx (1)

59-83: Query configuration and gating logic are correct.

The certificates query is properly gated behind premium tier and Android MDM checks (line 81), uses structured query keys for cache management, and follows the established react-query patterns in the codebase.

Comment thread frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx Outdated
@jacobshandling
jacobshandling marked this pull request as draft December 16, 2025 18:43
Comment thread frontend/interfaces/host.ts Outdated
export type HostCertStatus =
| "pending_removal"
| "removing"
| "removed"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will not have these 3. We will use operation_type=remove and pending/delivering/delivered instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so "verified" | "failed" | "pending" | "delivering" | "delivered"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My review is in progress. Going slow since I'm not that familiar with React/frontend.


return (
<SettingsSection title="Certificates">
<SettingsSection title="Certificate authorities">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

disabled={disableChildren}
className={`${baseClass}__card--add-button`}
type="button"
onClick={() => setShowModal(true)}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question. It seems this component knows more about the parent than it should. At the very least, wouldn't it be better for this line to be like onClick={onClick}?

Minor issue, but maybe you've discussed it in Frontend sync.
A random reference: https://matanbobi.dev/posts/stop-passing-setter-functions-to-components

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea we often will define a toggleModal function like const toggleModal = () => {setShowModal(!showModal)} but I feel it's excessive and writing it this way is very clear and concise

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at the article you linked. The issue it's flagging would be if here the Button component expected a setShowModal function passed to it that it would then call. This is not what's happening - we're already separating the abstractions by passing the callbackfunction () => setShowModal(true) to the onClick prop, which as you can see here makes very minimal assumptions about how that prop's value should look.

@jacobshandling jacobshandling Dec 17, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e., Button calls its onClick without needing to know anything about what it is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That typing IS a little messy now that I look at it, seems people were attempting to make it useful for both click events and keydown events, but that's a different issue

const nameInput = screen.getByPlaceholderText("VPN certificate");

const longName = "a".repeat(256);
await user.type(nameInput, longName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Typing 256 characters one by one feels slow. How about:

fireEvent.change(nameInput, { target: { value: longName } });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to test as close to the actual user interaction as possible, and in this case it would actually be typing in the name, not programatically changing the field's value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be click on the field and paste a long value. That would speed up the test and be something that an actual user could do.

@jacobshandling
jacobshandling marked this pull request as ready for review December 16, 2025 23:00

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a partial review. Approving since we need these changes for customer.

@jacobshandling
jacobshandling merged commit ca2fc60 into main Dec 16, 2025
17 checks passed
@jacobshandling
jacobshandling deleted the 36689-android-certs-os-settings-ui branch December 16, 2025 23:39
@coderabbitai coderabbitai Bot mentioned this pull request Mar 6, 2026
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
3 tasks
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.

Android certs: Controls -> OS settings UI

2 participants