← Files Codex Security CloudARCHIVED FILE
.internal/defense-factory-ui/src/monitoring-settings.tsx
26.9 KB · Oct 4, 2026 · 12:24 UTC
import type { components } from "@oai/aardvark-client/components";
import { Select } from "@oai/ds/ui/select";
import { Textarea } from "@oai/ds/ui/textarea";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { z } from "zod";
import { useCloud } from "./app-context";
import {
connectGithub,
logSecurityEvent,
openEnvironment,
securityClient,
} from "./client";
import { NotificationRules } from "./notifications";
import {
canSaveRepositoryEnvironment,
getRepositoryConnectionState,
matchesRepositoryEnvironment,
selectedRepositoryEnvironmentId,
} from "./repository-connector";
import { RequestState, Section } from "./ui";
import {
WorkbenchButton as Button,
WorkbenchInput as Input,
} from "./workbench/controls";
export function MonitoringSettings({
repoId,
configuration,
quotaUsed,
onSaved,
onDeleted,
}: {
repoId: string;
configuration?: components["schemas"]["AardvarkScanConfigurationResponse"];
quotaUsed: number | undefined;
onSaved: () => Promise<void>;
onDeleted: () => Promise<void>;
}) {
const cloud = useCloud();
const intl = useIntl();
const cache = useQueryClient();
const input: components["schemas"]["ContinuousScanInput"] | undefined =
configuration?.scan_type === "continuous_scan"
? { ...configuration.scan_input, scan_type: "continuous_scan" }
: undefined;
const [environmentId, setEnvironmentId] = useState(
configuration?.scan_input.environment_id ?? "",
);
const [enabled, setEnabled] = useState(input?.state ?? "enabled");
const [lookback, setLookback] = useState(
input?.lookback_days == null ? "" : String(input.lookback_days),
);
const [share, setShare] = useState(
configuration?.share_targets?.map((target) => target.email).join(", ") ??
"",
);
const initialOverview = input?.project_overview;
const [overview, setOverview] = useState(
readProjectOverview(initialOverview).text,
);
const [notifications, setNotifications] = useState<
components["schemas"]["AardvarkScanNotificationRuleResponse"][]
>(configuration?.notification_rules ?? []);
const [confirmDelete, setConfirmDelete] = useState(false);
const [saved, setSaved] = useState(false);
const [attackConcerns, setAttackConcerns] = useState("");
const [focusAreas, setFocusAreas] = useState("");
const [additionalContext, setAdditionalContext] = useState("");
const canWrite =
!configuration ||
cloud.identity?.isAdmin ||
configuration.owner_id === cloud.identity?.userId ||
configuration.permissions?.can_write;
const configuredConnectorId = configuration?.scan_input.repo_connector_id;
const { connectorId, status, canQuery } = getRepositoryConnectionState(
cloud,
repoId,
configuredConnectorId,
);
const canConfigureNotifications = cloud.notifications?.canConfigure === true;
const countLimit = cloud.scanLimits?.count ?? -1;
const lookbackLimit = cloud.scanLimits?.lookbackDays ?? -1;
const maxLookback = lookbackLimit < 0 ? 90 : Math.min(90, lookbackLimit);
const lookbackDays = lookback.trim() === "" ? null : Number(lookback);
const lookbackValid =
lookbackDays == null
? lookbackLimit < 0
: Number.isInteger(lookbackDays) &&
lookbackDays >= 0 &&
lookbackDays <= maxLookback;
const quotaExhausted =
!configuration &&
countLimit >= 0 &&
(quotaUsed == null || quotaUsed >= countLimit);
const rulesValid =
!canConfigureNotifications ||
notifications.every(
(rule) =>
rule.name.trim() !== "" &&
rule.destinations.length > 0 &&
rule.destinations.every(
(destination) =>
destination.slack_team_id !== "" &&
destination.channel_id !== "" &&
destination.channel_name !== "",
),
);
const {
mutate: connect,
error: connectionError,
isPending: connecting,
} = useMutation({
mutationFn: () => connectGithub(connectorId!),
onSuccess: () => cache.invalidateQueries({ queryKey: ["bootstrap"] }),
});
const { data: repository, error: repositoryError } = useQuery({
queryKey: ["repository", repoId, connectorId],
queryFn: () =>
securityClient.request({
operation: "github_get",
parameters: {
path: { repo_id: repoId },
query: { connector_id: connectorId ?? undefined },
},
}),
enabled: !configuration && canQuery,
staleTime: 60_000,
});
const {
data: environments,
error: environmentsError,
isPending: environmentsPending,
isFetching: environmentsFetching,
refetch: refreshEnvironments,
} = useQuery({
queryKey: [
"monitoring-environments",
cloud.accountId,
cloud.identity?.workspaceId,
cloud.identity?.userId,
repoId,
connectorId,
],
queryFn: () => securityClient.request({ operation: "environments_list" }),
staleTime: 60_000,
});
const compatible = environments?.filter((env) =>
matchesRepositoryEnvironment(env, repoId, connectorId),
);
const environmentsReady =
!environmentsPending && !environmentsFetching && !environmentsError;
const selectedEnvironmentId = configuration
? environmentId
: selectedRepositoryEnvironmentId(compatible, environmentId);
const canSaveEnvironment = canSaveRepositoryEnvironment({
environments: compatible,
discoveryReady: environmentsReady,
selectedId: selectedEnvironmentId,
configuredId: configuration?.scan_input.environment_id,
canCreate: !configuration && canQuery,
});
const {
mutate: editEnvironment,
error: editorError,
isPending: editing,
} = useMutation({
mutationFn: (id?: string) => {
if (!environmentsReady)
throw new Error("Wait for compatible Cloud environments to load");
return openEnvironment(id);
},
onSuccess: async ({ environmentId: id }) => {
await refreshEnvironments();
if (id) setEnvironmentId(id);
},
});
const {
mutate: save,
error,
isPending,
} = useMutation({
mutationFn: async () => {
if (!canSaveEnvironment)
throw new Error("Choose a compatible Cloud environment");
if (!configuration && (!repository || !cloud.identity || !canQuery))
throw new Error("The repository is not ready");
let savedEnvironmentId = selectedEnvironmentId;
if (!savedEnvironmentId) {
const created = await securityClient
.request({
operation: "environments_create_default",
parameters: {
path: { repo_id: repoId },
query: { connector_id: connectorId ?? undefined },
},
})
.catch((error: unknown) => {
// A concurrent creation may leave multiple choices for the user.
void refreshEnvironments();
throw error;
});
savedEnvironmentId = created.id;
// Retry monitoring with the existing environment if the next API call fails.
setEnvironmentId(created.id);
void refreshEnvironments();
}
const shareTargets = [
...new Set(
share
.split(",")
.map((email) => email.trim())
.filter(Boolean),
),
].map((email) => ({
type: "user" as const,
email,
permission: "editor" as const,
}));
const notificationRules = canConfigureNotifications
? notifications.map((rule) => ({
...rule,
destinations: rule.destinations.map(
({ slack_team_id, channel_id }) => ({
destination_type: "slack_channel" as const,
slack_team_id,
channel_id,
}),
),
}))
: undefined;
if (configuration)
return securityClient.request({
operation: "monitoring_update",
parameters: { path: { id: configuration.hid || configuration.id } },
requestBody: {
version: configuration.version,
environment_id: savedEnvironmentId,
state: enabled,
lookback_days: lookbackDays,
share_targets: shareTargets,
project_overview:
overview === readProjectOverview(initialOverview).text
? undefined
: editedProjectOverview(initialOverview, overview),
notification_rules: notificationRules,
},
});
if (!repository || !cloud.identity || !canQuery)
throw new Error("The repository is not ready");
return securityClient.request({
operation: "monitoring_create",
requestBody: {
environment_id: savedEnvironmentId,
repo_id: repoId,
repo_url: repository.clone_url,
repo_connector_id: connectorId,
owner_id: cloud.identity.userId,
scan_type: "continuous_scan",
state: enabled,
lookback_days: lookbackDays,
share_targets: shareTargets,
notification_rules: notificationRules,
maintainer_attack_concerns: attackConcerns || null,
maintainer_focus_areas: focusAreas || null,
maintainer_additional_context: additionalContext || null,
},
});
},
onSuccess: async () => {
logSecurityEvent({
name: "AardvarkScanConfigurationSaved",
data: {
repoId,
scanEnabled: enabled === "enabled",
isNewConfig: configuration == null,
lookbackDays: lookbackDays ?? undefined,
},
});
setSaved(true);
await Promise.all([
onSaved(),
cache.invalidateQueries({ queryKey: ["monitoring-metadata"] }),
]);
},
});
const {
mutate: remove,
error: deleteError,
isPending: deleting,
} = useMutation({
mutationFn: () =>
securityClient.request({
operation: "monitoring_delete",
parameters: {
path: { id: configuration?.hid || configuration?.id || "" },
},
}),
onSuccess: async () => {
await Promise.all([
onDeleted(),
cache.invalidateQueries({ queryKey: ["monitoring-metadata"] }),
cache.invalidateQueries({ queryKey: ["deleted-monitoring"] }),
]);
},
});
return (
<form
className="flex flex-col gap-6"
onSubmit={(e) => {
e.preventDefault();
if (
canWrite &&
canSaveEnvironment &&
lookbackValid &&
!quotaExhausted &&
rulesValid &&
!isPending &&
!deleting &&
(!!configuration || (canQuery && !!repository))
)
save();
}}
>
{!configuration && !canQuery && (
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.github"
defaultMessage="GitHub connection"
description="GitHub connection setup"
/>
}
>
{status === "connect" ? (
<>
<p className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.monitoringConnectGithub"
defaultMessage="Connect GitHub before configuring repository monitoring"
description="A GitHub connection is required to create monitoring"
/>
</p>
<Button
type="button"
color="outlineSurface"
loading={connecting}
onClick={() => connect()}
>
<FormattedMessage
id="defenseFactory.plugin.connectGithub"
defaultMessage="Connect GitHub"
description="Open GitHub connection setup"
/>
</Button>
</>
) : status === "disabled" ? (
<p role="status" className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.githubDisabled"
defaultMessage="Your workspace administrator has disabled GitHub connections"
description="GitHub cannot be connected under workspace policy"
/>
</p>
) : status === "loading" ? (
<RequestState />
) : (
<p role="status" className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.repositoryConnectionUnavailable"
defaultMessage="This repository's GitHub connection is unavailable. Check its connection and workspace access."
description="The specific GitHub or Enterprise connection required by this repository is unavailable"
/>
</p>
)}
</Section>
)}
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.continuousScanning"
defaultMessage="Continuous scanning"
description="Configure automatic scans of new repository commits"
/>
}
>
<p className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.monitoringDescription"
defaultMessage="Automatically scan new commits using the selected Cloud environment"
description="Continuous monitoring runs on the server"
/>
</p>
<fieldset
disabled={
!canWrite || isPending || deleting || (!configuration && !canQuery)
}
className="grid gap-5 md:grid-cols-2"
>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.monitoring"
defaultMessage="Monitoring"
description="Continuous scanning configuration"
/>
<Select
triggerClassName="df-control"
value={enabled}
options={[
{
value: "enabled",
label: intl.formatMessage({
id: "defenseFactory.plugin.enabled",
defaultMessage: "Enabled",
description: "Monitoring is enabled",
}),
},
{
value: "disabled",
label: intl.formatMessage({
id: "defenseFactory.plugin.paused",
defaultMessage: "Paused",
description: "Monitoring is paused",
}),
},
]}
onChange={(option) =>
setEnabled(option.value as "enabled" | "disabled")
}
/>
</label>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.lookback"
defaultMessage="Lookback days"
description="Number of days of commits to include when scanning starts"
/>
<Input
type="number"
min={0}
max={maxLookback}
aria-invalid={!lookbackValid}
value={lookback}
onChange={(e) => setLookback(e.target.value)}
/>
<span className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.lookbackHint"
defaultMessage="{unlimited, select, yes {Leave blank for all history, or enter 0–{maximum} days} other {Enter 0–{maximum} days}}"
description="Allowed lookback range for this workspace; some workspaces allow unlimited history"
values={{
unlimited: lookbackLimit < 0 ? "yes" : "no",
maximum: maxLookback,
}}
/>
</span>
</label>
<label className="flex flex-col gap-2 text-[14px] md:col-span-2">
<FormattedMessage
id="defenseFactory.plugin.cloudEnvironment"
defaultMessage="Cloud environment"
description="Cloud runtime environment for a scan"
/>
<Select
triggerClassName="df-control"
value={selectedEnvironmentId ?? ""}
loading={environmentsPending || environmentsFetching}
options={
compatible?.map((env) => ({
value: env.id,
label: env.label,
})) ?? []
}
onChange={(option) => setEnvironmentId(option.value)}
placeholder={intl.formatMessage({
id: "defenseFactory.plugin.chooseEnvironment",
defaultMessage: "Choose an environment",
description: "Choose a compatible Cloud environment",
})}
/>
</label>
<div className="flex gap-2 md:col-span-2">
<Button
color="outlineSurface"
onClick={() => editEnvironment(undefined)}
disabled={!environmentsReady}
loading={editing}
>
<FormattedMessage
id="defenseFactory.plugin.createEnvironment"
defaultMessage="Create environment"
description="Open the shared Cloud environment creation dialog"
/>
</Button>
{selectedEnvironmentId && (
<Button
color="ghostSecondary"
onClick={() => editEnvironment(selectedEnvironmentId)}
disabled={!environmentsReady}
>
<FormattedMessage
id="defenseFactory.plugin.editEnvironment"
defaultMessage="Edit environment"
description="Edit the selected Cloud environment"
/>
</Button>
)}
</div>
{!configuration && environmentsReady && compatible?.length === 0 && (
<p className="text-sm text-secondary md:col-span-2">
<FormattedMessage
id="defenseFactory.plugin.defaultMonitoringEnvironment"
defaultMessage="Saving will create a default Cloud environment for this repository"
description="Monitoring automatically creates its existing default environment when there is no compatible environment"
/>
</p>
)}
</fieldset>
{environmentsError && (
<RequestState
error={environmentsError}
retry={() => void refreshEnvironments()}
/>
)}
</Section>
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.sharing"
defaultMessage="Sharing"
description="Share editing access to repository monitoring"
/>
}
>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.editorEmails"
defaultMessage="Editor emails, separated by commas"
description="Email addresses of workspace members who may edit monitoring"
/>
<Input
value={share}
disabled={!canWrite || isPending || deleting}
onChange={(e) => setShare(e.target.value)}
/>
</label>
</Section>
{configuration && (
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.projectContext"
defaultMessage="Project context"
description="Additional repository context used for scanning"
/>
}
>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.threatModel"
defaultMessage="Threat model"
description="Editable threat model within the repository overview"
/>
<Textarea
value={overview}
disabled={!canWrite || isPending || deleting}
onChange={(e) => setOverview(e.target.value)}
/>
</label>
</Section>
)}
{!configuration && (
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.projectContext"
defaultMessage="Project context"
description="Additional repository context used for scanning"
/>
}
>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.attackConcerns"
defaultMessage="Attack concerns"
description="Repository maintainer's security concerns for the initial threat model"
/>
<Textarea
value={attackConcerns}
disabled={isPending || deleting}
onChange={(event) => setAttackConcerns(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.focusAreas"
defaultMessage="Focus areas"
description="Repository areas that need particular attention during security scanning"
/>
<Textarea
value={focusAreas}
disabled={isPending || deleting}
onChange={(event) => setFocusAreas(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-[14px]">
<FormattedMessage
id="defenseFactory.plugin.additionalContext"
defaultMessage="Additional context"
description="Additional repository maintainer context for the initial threat model"
/>
<Textarea
value={additionalContext}
disabled={isPending || deleting}
onChange={(event) => setAdditionalContext(event.target.value)}
/>
</label>
</Section>
)}
{canConfigureNotifications && (
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.notifications"
defaultMessage="Notifications"
description="Security finding notification rules"
/>
}
>
<NotificationRules
value={notifications}
onChange={setNotifications}
disabled={!canWrite || isPending || deleting}
/>
</Section>
)}
{(error ||
repositoryError ||
editorError ||
deleteError ||
connectionError) && (
<RequestState
error={
error ??
repositoryError ??
editorError ??
deleteError ??
connectionError
}
/>
)}
<div className="flex flex-wrap items-center justify-between gap-4">
{quotaExhausted && (
<p role="status" className="text-sm text-secondary">
<FormattedMessage
id="defenseFactory.plugin.monitoringQuota"
defaultMessage="This workspace has reached its limit of {count, number} monitored repositories"
description="The workspace cannot create more repository monitoring configurations"
values={{ count: countLimit }}
/>
</p>
)}
<div>
{configuration && canWrite && (
<Button color="danger" onClick={() => setConfirmDelete(true)}>
<FormattedMessage
id="defenseFactory.plugin.deleteMonitoring"
defaultMessage="Delete monitoring"
description="Remove the monitoring configuration"
/>
</Button>
)}
</div>
<div className="flex items-center gap-3">
{saved && (
<span role="status">
<FormattedMessage
id="defenseFactory.plugin.saved"
defaultMessage="Saved"
description="Changes were saved"
/>
</span>
)}
<Button
color="primary"
type="submit"
loading={isPending}
disabled={
!canWrite ||
deleting ||
quotaExhausted ||
!lookbackValid ||
!rulesValid ||
!canSaveEnvironment ||
(!configuration && (!repository || !canQuery))
}
>
<FormattedMessage
id="defenseFactory.plugin.save"
defaultMessage="Save"
description="Save changes"
/>
</Button>
</div>
</div>
{confirmDelete && (
<Section
title={
<FormattedMessage
id="defenseFactory.plugin.deleteConfirm"
defaultMessage="Delete this monitoring configuration?"
description="Confirm deleting repository monitoring"
/>
}
>
<p>
<FormattedMessage
id="defenseFactory.plugin.recoveryWindow.repositoriesMenu"
defaultMessage="You can restore it for 30 days from the Repositories menu under Restore deleted monitoring."
description="Monitoring recovery period and location of the restore action in the repository overflow menu."
/>
</p>
<div className="flex gap-3">
<Button
color="dangerSolid"
onClick={() => remove()}
loading={deleting}
>
<FormattedMessage
id="defenseFactory.plugin.deleteMonitoring"
defaultMessage="Delete monitoring"
description="Remove the monitoring configuration"
/>
</Button>
<Button
color="ghostSecondary"
onClick={() => setConfirmDelete(false)}
>
<FormattedMessage
id="defenseFactory.plugin.cancel"
defaultMessage="Cancel"
description="Cancel the current action"
/>
</Button>
</div>
</Section>
)}
</form>
);
}
/** Preserve the machine-generated fields alongside the editable threat model. */
function readProjectOverview(raw: string | null | undefined): {
text: string;
record: Record<string, unknown> | null;
} {
if (!raw) return { text: "", record: null };
try {
const result = z
.looseObject({ threat_model: z.string().nullable().optional() })
.safeParse(JSON.parse(raw));
return result.success
? { text: result.data.threat_model ?? "", record: result.data }
: { text: raw, record: null };
} catch {
return { text: raw, record: null };
}
}
function editedProjectOverview(
raw: string | null | undefined,
threatModel: string,
): string | null {
const { record } = readProjectOverview(raw);
if (
!threatModel &&
(!record || Object.keys(record).every((key) => key === "threat_model"))
)
return null;
return JSON.stringify({ ...record, threat_model: threatModel || null });
}
SHA-256: ee36b53e29bc76bef2a3d05fbbea25f02612a3b4d927d345c870aa58a0771a1b