Added Android cert activity logging (#42608)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #37546 Noah approved design here: https://fleetdm.slack.com/archives/C09HG9VMRSS/p1774895365069069?thread_ts=1774647818.613419&cid=C09HG9VMRSS Docs: https://github.com/fleetdm/fleet/pull/42609 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Certificate installation activity logging for Android hosts (success & failure) * "Installed certificate" activity type displayed in host activity feed with status * Host activity card enabled on Android host details page to show installs * Detailed install modal with error detail reveal when failures occur * Upcoming activities tab disabled for Android hosts where unsupported <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
* Added activity logging when a certificate is installed or fails to install on an Android host.
|
||||
* Enabled the host activity card on the Android host details page.
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import ModalFooter from "components/ModalFooter";
|
||||
import Button from "components/buttons/Button";
|
||||
import IconStatusMessage from "components/IconStatusMessage";
|
||||
import Textarea from "components/Textarea";
|
||||
import RevealButton from "components/buttons/RevealButton";
|
||||
|
||||
const baseClass = "certificate-install-details-modal";
|
||||
|
||||
export interface ICertificateInstallDetails {
|
||||
certificateName: string;
|
||||
hostDisplayName: string;
|
||||
status: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
interface ICertificateInstallDetailsModalProps {
|
||||
details: ICertificateInstallDetails;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const CertificateInstallDetailsModal = ({
|
||||
details,
|
||||
onCancel,
|
||||
}: ICertificateInstallDetailsModalProps) => {
|
||||
const { certificateName, hostDisplayName, detail } = details;
|
||||
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
const formattedHost = hostDisplayName ? <b>{hostDisplayName}</b> : "the host";
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Install details"
|
||||
onExit={onCancel}
|
||||
onEnter={onCancel}
|
||||
className={baseClass}
|
||||
>
|
||||
<div className={`${baseClass}__modal-content`}>
|
||||
<IconStatusMessage
|
||||
className={`${baseClass}__status-message`}
|
||||
iconName="error"
|
||||
message={
|
||||
<span>
|
||||
Fleet failed to install certificate <b>{certificateName}</b> on{" "}
|
||||
{formattedHost}.
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{detail && (
|
||||
<>
|
||||
<RevealButton
|
||||
isShowing={showDetails}
|
||||
showText="Details"
|
||||
hideText="Details"
|
||||
caretPosition="after"
|
||||
onClick={() => setShowDetails((prev) => !prev)}
|
||||
/>
|
||||
{showDetails && (
|
||||
<Textarea label="Error details:" variant="code">
|
||||
{detail}
|
||||
</Textarea>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ModalFooter primaryButtons={<Button onClick={onCancel}>Done</Button>} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CertificateInstallDetailsModal;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
.certificate-install-details-modal {
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
&__modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $pad-medium;
|
||||
}
|
||||
|
||||
.reveal-button {
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export { default } from "./CertificateInstallDetailsModal";
|
||||
export type { ICertificateInstallDetails } from "./CertificateInstallDetailsModal";
|
||||
@@ -160,6 +160,7 @@ export enum ActivityType {
|
||||
EditedHostIdpData = "edited_host_idp_data",
|
||||
AddedCertificate = "added_certificate",
|
||||
DeletedCertificate = "deleted_certificate",
|
||||
InstalledCertificate = "installed_certificate",
|
||||
EditedEnrollSecrets = "edited_enroll_secrets",
|
||||
AddedMicrosoftEntraTenant = "added_microsoft_entra_tenant",
|
||||
DeletedMicrosoftEntraTenant = "deleted_microsoft_entra_tenant",
|
||||
@@ -181,7 +182,9 @@ export type IHostPastActivityType =
|
||||
| ActivityType.CanceledRunScript
|
||||
| ActivityType.CanceledInstallAppStoreApp
|
||||
| ActivityType.CanceledInstallSoftware
|
||||
| ActivityType.CanceledUninstallSoftware;
|
||||
| ActivityType.CanceledUninstallSoftware
|
||||
| ActivityType.InstalledCertificate
|
||||
| ActivityType.ResentCertificate;
|
||||
|
||||
/** This is a subset of ActivityType that are shown only for the host upcoming activities */
|
||||
export type IHostUpcomingActivityType =
|
||||
@@ -289,6 +292,7 @@ export interface IActivityDetails {
|
||||
tenant_id?: string;
|
||||
certificate_name?: string;
|
||||
certificate_template_id?: number;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
// maps activity types to their corresponding label to use when filtering activites via the dropdown
|
||||
@@ -459,5 +463,6 @@ export const ACTIVITY_TYPE_TO_FILTER_LABEL: Record<ActivityType, string> = {
|
||||
[ActivityType.EditedHostIdpData]: "Edited host identity provider (IdP) data",
|
||||
[ActivityType.AddedCertificate]: "Added certificate",
|
||||
[ActivityType.DeletedCertificate]: "Deleted certificate",
|
||||
[ActivityType.InstalledCertificate]: "Installed certificate",
|
||||
[ActivityType.EditedEnrollSecrets]: "Edited enroll secrets",
|
||||
};
|
||||
|
||||
@@ -94,6 +94,9 @@ import SoftwareUninstallDetailsModal, {
|
||||
ISWUninstallDetailsParentState,
|
||||
} from "components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal";
|
||||
import { IShowActivityDetailsData } from "components/ActivityItem/ActivityItem";
|
||||
import CertificateInstallDetailsModal, {
|
||||
ICertificateInstallDetails,
|
||||
} from "components/ActivityDetails/InstallDetails/CertificateInstallDetailsModal";
|
||||
import { getDisplayedSoftwareName } from "pages/SoftwarePage/helpers";
|
||||
|
||||
import CommandResultsModal from "pages/hosts/components/CommandDetailsModal";
|
||||
@@ -268,6 +271,10 @@ const HostDetailsPage = ({
|
||||
activityVPPInstallDetails,
|
||||
setActivityVPPInstallDetails,
|
||||
] = useState<IVppInstallDetails | null>(null);
|
||||
const [
|
||||
certificateInstallDetails,
|
||||
setCertificateInstallDetails,
|
||||
] = useState<ICertificateInstallDetails | null>(null);
|
||||
const [mdmCommandDetails, setMdmCommandDetails] = useState<ICommand | null>(
|
||||
null
|
||||
);
|
||||
@@ -842,6 +849,15 @@ const HostDetailsPage = ({
|
||||
platform: details?.host_platform || host?.platform,
|
||||
});
|
||||
break;
|
||||
case "installed_certificate":
|
||||
setCertificateInstallDetails({
|
||||
certificateName: details?.certificate_name || "",
|
||||
hostDisplayName:
|
||||
host?.display_name || details?.host_display_name || "",
|
||||
status: details?.status || "",
|
||||
detail: details?.detail || "",
|
||||
});
|
||||
break;
|
||||
default: // do nothing
|
||||
}
|
||||
},
|
||||
@@ -1151,7 +1167,6 @@ const HostDetailsPage = ({
|
||||
|
||||
const showSoftwareLibraryTab = isPremiumTier;
|
||||
const showReportsTab = mdm?.enrollment_status !== "Pending";
|
||||
const showActivityCard = !isAndroidHost;
|
||||
const showAgentOptionsCard = !isIosOrIpadosHost && !isAndroidHost;
|
||||
const showLocalUserAccountsCard = !isIosOrIpadosHost && !isAndroidHost;
|
||||
const showCertificatesCard =
|
||||
@@ -1354,64 +1369,62 @@ const HostDetailsPage = ({
|
||||
toggleLocationModal={toggleLocationModal}
|
||||
toggleMDMStatusModal={toggleMDMStatusModal}
|
||||
/>
|
||||
{showActivityCard && (
|
||||
<ActivityCard
|
||||
className={
|
||||
showAgentOptionsCard
|
||||
? tripleHeightCardClass
|
||||
: defaultCardClass
|
||||
}
|
||||
activeTab={activeActivityTab}
|
||||
activities={
|
||||
activeActivityTab === "past"
|
||||
? pastActivities
|
||||
: upcomingActivities
|
||||
}
|
||||
commands={
|
||||
activeActivityTab === "past"
|
||||
? pastMDMCommands
|
||||
: upcomingMDMCommands
|
||||
}
|
||||
isLoading={
|
||||
activeActivityTab === "past"
|
||||
? pastActivitiesIsFetching || pastMDMCommandsIsFetching
|
||||
: upcomingActivitiesIsFetching ||
|
||||
upcomingMDMCommandsIsFetching
|
||||
}
|
||||
isError={
|
||||
activeActivityTab === "past"
|
||||
? pastActivitiesIsError || pastMDMCommandsIsError
|
||||
: upcomingActivitiesIsError ||
|
||||
upcomingMDMCommandsIsError
|
||||
}
|
||||
canCancelActivities={
|
||||
isGlobalAdmin ||
|
||||
isGlobalMaintainer ||
|
||||
isHostTeamAdmin ||
|
||||
isHostTeamMaintainer
|
||||
}
|
||||
showMDMCommandsToggle={canGetMDMCommands}
|
||||
showMDMCommands={showMDMCommands}
|
||||
onShowMDMCommands={() => {
|
||||
setActivityPage(0);
|
||||
setShowMDMCommands(true);
|
||||
}}
|
||||
onHideMDMCommands={() => {
|
||||
setActivityPage(0);
|
||||
setShowMDMCommands(false);
|
||||
}}
|
||||
upcomingCount={
|
||||
(upcomingActivities?.count || 0) +
|
||||
(upcomingMDMCommands?.count || 0)
|
||||
}
|
||||
onChangeTab={onChangeActivityTab}
|
||||
onNextPage={() => setActivityPage(activityPage + 1)}
|
||||
onPreviousPage={() => setActivityPage(activityPage - 1)}
|
||||
onShowDetails={onShowActivityDetails}
|
||||
onShowCommandDetails={setMdmCommandDetails}
|
||||
onCancel={onCancelActivity}
|
||||
/>
|
||||
)}
|
||||
<ActivityCard
|
||||
className={
|
||||
showAgentOptionsCard
|
||||
? tripleHeightCardClass
|
||||
: defaultCardClass
|
||||
}
|
||||
activeTab={activeActivityTab}
|
||||
activities={
|
||||
activeActivityTab === "past"
|
||||
? pastActivities
|
||||
: upcomingActivities
|
||||
}
|
||||
commands={
|
||||
activeActivityTab === "past"
|
||||
? pastMDMCommands
|
||||
: upcomingMDMCommands
|
||||
}
|
||||
isLoading={
|
||||
activeActivityTab === "past"
|
||||
? pastActivitiesIsFetching || pastMDMCommandsIsFetching
|
||||
: upcomingActivitiesIsFetching ||
|
||||
upcomingMDMCommandsIsFetching
|
||||
}
|
||||
isError={
|
||||
activeActivityTab === "past"
|
||||
? pastActivitiesIsError || pastMDMCommandsIsError
|
||||
: upcomingActivitiesIsError || upcomingMDMCommandsIsError
|
||||
}
|
||||
canCancelActivities={
|
||||
isGlobalAdmin ||
|
||||
isGlobalMaintainer ||
|
||||
isHostTeamAdmin ||
|
||||
isHostTeamMaintainer
|
||||
}
|
||||
isUpcomingDisabled={isAndroidHost}
|
||||
showMDMCommandsToggle={canGetMDMCommands}
|
||||
showMDMCommands={showMDMCommands}
|
||||
onShowMDMCommands={() => {
|
||||
setActivityPage(0);
|
||||
setShowMDMCommands(true);
|
||||
}}
|
||||
onHideMDMCommands={() => {
|
||||
setActivityPage(0);
|
||||
setShowMDMCommands(false);
|
||||
}}
|
||||
upcomingCount={
|
||||
(upcomingActivities?.count || 0) +
|
||||
(upcomingMDMCommands?.count || 0)
|
||||
}
|
||||
onChangeTab={onChangeActivityTab}
|
||||
onNextPage={() => setActivityPage(activityPage + 1)}
|
||||
onPreviousPage={() => setActivityPage(activityPage - 1)}
|
||||
onShowDetails={onShowActivityDetails}
|
||||
onShowCommandDetails={setMdmCommandDetails}
|
||||
onCancel={onCancelActivity}
|
||||
/>
|
||||
<UserCard
|
||||
className={defaultCardClass}
|
||||
endUsers={host.end_users ?? []}
|
||||
@@ -1430,11 +1443,7 @@ const HostDetailsPage = ({
|
||||
}}
|
||||
/>
|
||||
<LabelsCard
|
||||
className={
|
||||
!showActivityCard && !showAgentOptionsCard
|
||||
? fullWidthCardClass
|
||||
: defaultCardClass
|
||||
}
|
||||
className={defaultCardClass}
|
||||
labels={host?.labels || []}
|
||||
onLabelClick={onLabelClick}
|
||||
/>
|
||||
@@ -1651,6 +1660,12 @@ const HostDetailsPage = ({
|
||||
onCancel={onCancelVppInstallDetailsModal}
|
||||
/>
|
||||
)}
|
||||
{!!certificateInstallDetails && (
|
||||
<CertificateInstallDetailsModal
|
||||
details={certificateInstallDetails}
|
||||
onCancel={() => setCertificateInstallDetails(null)}
|
||||
/>
|
||||
)}
|
||||
{!!mdmCommandDetails && (
|
||||
<CommandResultsModal
|
||||
command={mdmCommandDetails}
|
||||
|
||||
@@ -58,6 +58,8 @@ interface IActivityProps {
|
||||
* upcoming activities and mdm commands. */
|
||||
upcomingCount: number;
|
||||
canCancelActivities: boolean;
|
||||
/** When true, the Upcoming tab is disabled with a tooltip */
|
||||
isUpcomingDisabled?: boolean;
|
||||
onChangeTab: (index: number, last: number, event: Event) => void;
|
||||
onNextPage: () => void;
|
||||
onPreviousPage: () => void;
|
||||
@@ -79,6 +81,7 @@ const Activity = ({
|
||||
className,
|
||||
upcomingCount,
|
||||
canCancelActivities,
|
||||
isUpcomingDisabled = false,
|
||||
onChangeTab,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
@@ -116,8 +119,18 @@ const Activity = ({
|
||||
<Tab>
|
||||
<TabText>Past</TabText>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<TabText count={upcomingCount}>Upcoming</TabText>
|
||||
<Tab disabled={isUpcomingDisabled}>
|
||||
{isUpcomingDisabled ? (
|
||||
<TooltipWrapper
|
||||
tipContent="Currently, upcoming activity is only supported for macOS, Windows, Linux, iOS, and iPadOS hosts."
|
||||
showArrow
|
||||
underline={false}
|
||||
>
|
||||
<TabText>Upcoming</TabText>
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<TabText count={upcomingCount}>Upcoming</TabText>
|
||||
)}
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className={`${baseClass}__tab-panel`}>
|
||||
|
||||
@@ -22,6 +22,8 @@ import InstalledSoftwareActivityItem from "./ActivityItems/InstalledSoftwareActi
|
||||
import CanceledRunScriptActivityItem from "./ActivityItems/CanceledRunScriptActivityItem";
|
||||
import CanceledInstallSoftwareActivityItem from "./ActivityItems/CanceledInstallSoftwareActivityItem";
|
||||
import CanceledUninstallSoftwareActivtyItem from "./ActivityItems/CanceledUninstallSoftwareActivtyItem";
|
||||
import InstalledCertificateActivityItem from "./ActivityItems/InstalledCertificateActivityItem";
|
||||
import ResentCertificateActivityItem from "./ActivityItems/ResentCertificateActivityItem";
|
||||
|
||||
/** The component props that all host activity items must adhere to */
|
||||
export interface IHostActivityItemComponentProps {
|
||||
@@ -64,6 +66,8 @@ export const pastActivityComponentMap: Record<
|
||||
[ActivityType.CanceledInstallSoftware]: CanceledInstallSoftwareActivityItem,
|
||||
[ActivityType.CanceledInstallAppStoreApp]: CanceledInstallSoftwareActivityItem,
|
||||
[ActivityType.CanceledUninstallSoftware]: CanceledUninstallSoftwareActivtyItem,
|
||||
[ActivityType.InstalledCertificate]: InstalledCertificateActivityItem,
|
||||
[ActivityType.ResentCertificate]: ResentCertificateActivityItem,
|
||||
};
|
||||
|
||||
export const upcomingActivityComponentMap: Record<
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
|
||||
import ActivityItem from "components/ActivityItem";
|
||||
|
||||
import { IHostActivityItemComponentPropsWithShowDetails } from "../../ActivityConfig";
|
||||
|
||||
const baseClass = "installed-certificate-activity-item";
|
||||
|
||||
const InstalledCertificateActivityItem = ({
|
||||
activity,
|
||||
onShowDetails,
|
||||
}: IHostActivityItemComponentPropsWithShowDetails) => {
|
||||
const isFailed = activity.details?.status === "failed_install";
|
||||
|
||||
return (
|
||||
<ActivityItem
|
||||
className={baseClass}
|
||||
activity={activity}
|
||||
hideCancel
|
||||
hideShowDetails={!isFailed}
|
||||
onShowDetails={onShowDetails}
|
||||
>
|
||||
{isFailed ? (
|
||||
<>
|
||||
<b>Fleet</b> failed to install certificate{" "}
|
||||
<b>{activity.details?.certificate_name}</b> on this host.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<b>Fleet</b> installed certificate{" "}
|
||||
<b>{activity.details?.certificate_name}</b> on this host.
|
||||
</>
|
||||
)}
|
||||
</ActivityItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstalledCertificateActivityItem;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { default } from "./InstalledCertificateActivityItem";
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
import ActivityItem from "components/ActivityItem";
|
||||
|
||||
import { IHostActivityItemComponentProps } from "../../ActivityConfig";
|
||||
|
||||
const baseClass = "resent-certificate-activity-item";
|
||||
|
||||
const ResentCertificateActivityItem = ({
|
||||
activity,
|
||||
}: IHostActivityItemComponentProps) => {
|
||||
return (
|
||||
<ActivityItem
|
||||
className={baseClass}
|
||||
activity={activity}
|
||||
hideCancel
|
||||
hideShowDetails
|
||||
>
|
||||
<b>{activity.actor_full_name}</b> resent{" "}
|
||||
<b>{activity.details?.certificate_name}</b> certificate on this host.
|
||||
</ActivityItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResentCertificateActivityItem;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { default } from "./ResentCertificateActivityItem";
|
||||
@@ -167,6 +167,7 @@ var ActivityDetailsList = []ActivityDetails{
|
||||
ActivityTypeEditedAndroidProfile{},
|
||||
ActivityTypeEditedAndroidCertificate{},
|
||||
ActivityTypeResentCertificate{},
|
||||
ActivityTypeInstalledCertificate{},
|
||||
|
||||
ActivityTypeResentConfigurationProfile{},
|
||||
ActivityTypeResentConfigurationProfileBatch{},
|
||||
@@ -1826,3 +1827,28 @@ type ActivityTypeEditedEnrollSecrets struct {
|
||||
func (a ActivityTypeEditedEnrollSecrets) ActivityName() string {
|
||||
return "edited_enroll_secrets"
|
||||
}
|
||||
|
||||
type ActivityTypeInstalledCertificate struct {
|
||||
HostID uint `json:"host_id"`
|
||||
HostDisplayName string `json:"host_display_name"`
|
||||
CertificateTemplateID uint `json:"certificate_template_id"`
|
||||
CertificateName string `json:"certificate_name"`
|
||||
Status string `json:"status"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeInstalledCertificate) ActivityName() string {
|
||||
return "installed_certificate"
|
||||
}
|
||||
|
||||
func (a ActivityTypeInstalledCertificate) HostIDs() []uint {
|
||||
return []uint{a.HostID}
|
||||
}
|
||||
|
||||
func (a ActivityTypeInstalledCertificate) WasFromAutomation() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a ActivityTypeInstalledCertificate) HostOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ var (
|
||||
CertificateTemplateVerified CertificateTemplateStatus = "verified"
|
||||
)
|
||||
|
||||
// CertificateActivityStatus represents the status of a certificate install activity.
|
||||
type CertificateActivityStatus string
|
||||
|
||||
const (
|
||||
CertificateActivityInstalled CertificateActivityStatus = "installed"
|
||||
CertificateActivityFailedInstall CertificateActivityStatus = "failed_install"
|
||||
)
|
||||
|
||||
// CertificateTemplateStatusToMDMDeliveryStatus converts a CertificateTemplateStatus to MDMDeliveryStatus.
|
||||
// This is used when converting HostCertificateTemplate to HostMDMProfile for the GetHost endpoint.
|
||||
func CertificateTemplateStatusToMDMDeliveryStatus(s CertificateTemplateStatus) MDMDeliveryStatus {
|
||||
|
||||
@@ -695,7 +695,41 @@ func (svc *Service) UpdateCertificateStatus(ctx context.Context, update *fleet.C
|
||||
|
||||
// Fill in HostUUID from context
|
||||
update.HostUUID = host.UUID
|
||||
return svc.ds.UpsertCertificateStatus(ctx, update)
|
||||
if err := svc.ds.UpsertCertificateStatus(ctx, update); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Log activity for terminal install statuses only (not removals).
|
||||
if update.OperationType == fleet.MDMOperationTypeInstall {
|
||||
var actStatus fleet.CertificateActivityStatus
|
||||
switch update.Status {
|
||||
case fleet.MDMDeliveryVerified:
|
||||
actStatus = fleet.CertificateActivityInstalled
|
||||
case fleet.MDMDeliveryFailed:
|
||||
actStatus = fleet.CertificateActivityFailedInstall
|
||||
}
|
||||
if actStatus != "" {
|
||||
detail := ""
|
||||
if update.Detail != nil {
|
||||
detail = *update.Detail
|
||||
}
|
||||
if err := svc.NewActivity(ctx, nil, fleet.ActivityTypeInstalledCertificate{
|
||||
HostID: host.ID,
|
||||
HostDisplayName: host.DisplayName(),
|
||||
CertificateTemplateID: update.CertificateTemplateID,
|
||||
CertificateName: record.Name,
|
||||
Status: string(actStatus),
|
||||
Detail: detail,
|
||||
}); err != nil {
|
||||
// Log and continue since we don't want the client to retry.
|
||||
svc.logger.ErrorContext(ctx, "failed to create certificate install activity", "host.id", host.ID, "activity.status", actStatus,
|
||||
"err", err)
|
||||
ctxerr.Handle(ctx, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user