mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-11 15:10:39 +00:00
Extract audit webhook runtime owner
This commit is contained in:
@@ -32,11 +32,12 @@ work extends shared components instead of creating new local variants.
|
||||
10. `frontend-modern/src/components/Settings/AuditLogPanel.tsx`
|
||||
11. `frontend-modern/src/components/Settings/useAuditLogPanelState.ts`
|
||||
12. `frontend-modern/src/components/Settings/AuditWebhookPanel.tsx`
|
||||
13. `frontend-modern/src/components/Settings/CopyCommandBlock.tsx`
|
||||
14. `frontend-modern/src/components/Settings/diagnosticsModel.ts`
|
||||
15. `frontend-modern/src/components/Settings/DiagnosticsPanel.tsx`
|
||||
16. `frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx`
|
||||
17. `frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx`
|
||||
13. `frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts`
|
||||
14. `frontend-modern/src/components/Settings/CopyCommandBlock.tsx`
|
||||
15. `frontend-modern/src/components/Settings/diagnosticsModel.ts`
|
||||
16. `frontend-modern/src/components/Settings/DiagnosticsPanel.tsx`
|
||||
17. `frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx`
|
||||
18. `frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx`
|
||||
18. `frontend-modern/src/components/Settings/NetworkSettingsPanel.tsx`
|
||||
19. `frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx`
|
||||
20. `frontend-modern/src/components/Settings/SecurityAuthPanel.tsx`
|
||||
@@ -136,6 +137,14 @@ license/paywall lifecycle, persisted filters, verification flow, and audit-log
|
||||
fetch orchestration. The shell must not re-accumulate localStorage or API
|
||||
runtime logic inline.
|
||||
|
||||
The audit webhook settings surface now follows that same owner split.
|
||||
`frontend-modern/src/components/Settings/AuditWebhookPanel.tsx` stays the
|
||||
canonical `SettingsPanel` shell, while
|
||||
`frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts` owns the
|
||||
license/paywall lifecycle, webhook fetch/save flow, validation, and trial
|
||||
startup orchestration. The shell must not re-accumulate API calls or paywall
|
||||
tracking inline.
|
||||
|
||||
The diagnostics settings surface now follows that same owner split.
|
||||
`frontend-modern/src/components/Settings/DiagnosticsPanel.tsx` stays the
|
||||
top-level diagnostics shell, while
|
||||
|
||||
@@ -1668,6 +1668,7 @@
|
||||
"frontend-modern/src/components/Settings/updatesSettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/useAuditLogPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useDiagnosticsPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useSSOProvidersState.ts",
|
||||
"frontend-modern/src/components/SetupWizard/SetupCompletionPreview.tsx",
|
||||
@@ -1736,6 +1737,7 @@
|
||||
"frontend-modern/src/components/Settings/updatesSettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/useAuditLogPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useDiagnosticsPanelState.ts",
|
||||
"frontend-modern/src/components/Settings/useSSOProvidersState.ts"
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSignal, For, onMount, Show, createEffect, type Component } from 'solid-js';
|
||||
import { For, Show, type Component } from 'solid-js';
|
||||
import Shield from 'lucide-solid/icons/shield';
|
||||
import Globe from 'lucide-solid/icons/globe';
|
||||
import Plus from 'lucide-solid/icons/plus';
|
||||
@@ -7,150 +7,49 @@ import ExternalLink from 'lucide-solid/icons/external-link';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import { formControl } from '@/components/shared/Form';
|
||||
import { showSuccess, showWarning } from '@/utils/toast';
|
||||
import { apiFetchJSON } from '@/utils/apiClient';
|
||||
import {
|
||||
AUDIT_WEBHOOK_ENDPOINT_CARD_CLASS,
|
||||
AUDIT_WEBHOOK_ENDPOINT_ICON_CLASS,
|
||||
AUDIT_WEBHOOK_READONLY_NOTICE_CLASS,
|
||||
AUDIT_WEBHOOK_SECURITY_NOTE_BODY,
|
||||
AUDIT_WEBHOOK_SECURITY_NOTE_TITLE,
|
||||
getAuditWebhookEmptyStateCopy,
|
||||
getAuditWebhookFeatureGateCopy,
|
||||
getAuditWebhookLoadingState,
|
||||
} from '@/utils/auditWebhookPresentation';
|
||||
import { trackUpgradeClicked } from '@/utils/upgradeMetrics';
|
||||
import {
|
||||
hasFeature,
|
||||
licenseLoaded,
|
||||
loadLicenseStatus,
|
||||
getUpgradeActionUrlOrFallback,
|
||||
startProTrial,
|
||||
entitlements,
|
||||
} from '@/stores/license';
|
||||
import { trackPaywallViewed, trackUpgradeClicked } from '@/utils/upgradeMetrics';
|
||||
import {
|
||||
getProTrialStartedMessage,
|
||||
getTrialAlreadyUsedMessage,
|
||||
getTrialStartErrorMessage,
|
||||
getUpgradeActionButtonClass,
|
||||
UPGRADE_ACTION_LABEL,
|
||||
UPGRADE_TRIAL_LABEL,
|
||||
UPGRADE_TRIAL_LINK_CLASS,
|
||||
} from '@/utils/upgradePresentation';
|
||||
import { useAuditWebhookPanelState } from '@/components/Settings/useAuditWebhookPanelState';
|
||||
|
||||
interface AuditWebhookPanelProps {
|
||||
canManage?: boolean;
|
||||
}
|
||||
|
||||
export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
const [webhookUrls, setWebhookUrls] = createSignal<string[]>([]);
|
||||
const [newUrl, setNewUrl] = createSignal('');
|
||||
const [saving, setSaving] = createSignal(false);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
const [startingTrial, setStartingTrial] = createSignal(false);
|
||||
const canManage = () => props.canManage !== false;
|
||||
const {
|
||||
canManage,
|
||||
canStartTrial,
|
||||
handleAddWebhook,
|
||||
handleRemoveWebhook,
|
||||
handleStartTrial,
|
||||
isAuditLoggingEnabled,
|
||||
loading,
|
||||
newUrl,
|
||||
saving,
|
||||
setNewUrl,
|
||||
startingTrial,
|
||||
upgradeActionUrl,
|
||||
webhookUrls,
|
||||
} = useAuditWebhookPanelState(props.canManage);
|
||||
const featureGateCopy = () => getAuditWebhookFeatureGateCopy();
|
||||
const emptyStateCopy = () => getAuditWebhookEmptyStateCopy();
|
||||
|
||||
const canStartTrial = () => entitlements()?.trial_eligible !== false;
|
||||
|
||||
const handleStartTrial = async () => {
|
||||
if (startingTrial()) return;
|
||||
setStartingTrial(true);
|
||||
try {
|
||||
const result = await startProTrial();
|
||||
if (result?.outcome === 'redirect') {
|
||||
window.location.href = result.actionUrl;
|
||||
return;
|
||||
}
|
||||
showSuccess(getProTrialStartedMessage());
|
||||
} catch (err) {
|
||||
const statusCode = (err as { status?: number } | null)?.status;
|
||||
if (statusCode === 409) {
|
||||
showWarning(getTrialAlreadyUsedMessage());
|
||||
} else {
|
||||
showWarning(getTrialStartErrorMessage(err instanceof Error ? err.message : undefined));
|
||||
}
|
||||
} finally {
|
||||
setStartingTrial(false);
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
loadLicenseStatus();
|
||||
});
|
||||
|
||||
createEffect((wasPaywallVisible: boolean) => {
|
||||
const isPaywallVisible = licenseLoaded() && !hasFeature('audit_logging');
|
||||
if (isPaywallVisible && !wasPaywallVisible) {
|
||||
trackPaywallViewed('audit_logging', 'settings_audit_webhook_panel');
|
||||
}
|
||||
return isPaywallVisible;
|
||||
}, false);
|
||||
|
||||
createEffect(() => {
|
||||
if (hasFeature('audit_logging')) {
|
||||
fetchWebhooks();
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchWebhooks = async () => {
|
||||
try {
|
||||
const data = await apiFetchJSON<{ urls: string[] }>('/api/admin/webhooks/audit');
|
||||
setWebhookUrls(data.urls || []);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch audit webhooks:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddWebhook = async () => {
|
||||
if (!canManage()) return;
|
||||
const url = newUrl().trim();
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
new URL(url); // basic validation
|
||||
} catch {
|
||||
showWarning('Please enter a valid URL');
|
||||
return;
|
||||
}
|
||||
|
||||
if (webhookUrls().includes(url)) {
|
||||
showWarning('This URL is already configured');
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = [...webhookUrls(), url];
|
||||
await saveWebhooks(updated);
|
||||
setNewUrl('');
|
||||
};
|
||||
|
||||
const handleRemoveWebhook = async (url: string) => {
|
||||
if (!canManage()) return;
|
||||
const updated = webhookUrls().filter((u) => u !== url);
|
||||
await saveWebhooks(updated);
|
||||
};
|
||||
|
||||
const saveWebhooks = async (urls: string[]) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await apiFetchJSON('/api/admin/webhooks/audit', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ urls }),
|
||||
});
|
||||
|
||||
setWebhookUrls(urls);
|
||||
showSuccess('Audit webhooks updated');
|
||||
} catch (_err) {
|
||||
showWarning('Failed to save webhook configuration');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!hasFeature('audit_logging')) {
|
||||
if (!isAuditLoggingEnabled()) {
|
||||
return (
|
||||
<SettingsPanel
|
||||
title="Audit Webhooks"
|
||||
@@ -168,7 +67,7 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row items-center gap-2">
|
||||
<a
|
||||
href={getUpgradeActionUrlOrFallback('audit_logging')}
|
||||
href={upgradeActionUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class={getUpgradeActionButtonClass()}
|
||||
@@ -279,12 +178,10 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-amber-900 dark:text-amber-100 mb-1.5">
|
||||
Security Note
|
||||
{AUDIT_WEBHOOK_SECURITY_NOTE_TITLE}
|
||||
</h3>
|
||||
<p class="text-sm text-amber-800 dark:text-amber-200 leading-relaxed">
|
||||
Audit webhooks are dispatched asynchronously to avoid blocking user operations.
|
||||
Endpoints should still verify source trust (for example via an ingest secret) before
|
||||
processing events.
|
||||
{AUDIT_WEBHOOK_SECURITY_NOTE_BODY}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import apiAccessPanelSource from '../APIAccessPanel.tsx?raw';
|
||||
import auditLogPanelSource from '../AuditLogPanel.tsx?raw';
|
||||
import auditLogStateSource from '../useAuditLogPanelState.ts?raw';
|
||||
import auditWebhookPanelSource from '../AuditWebhookPanel.tsx?raw';
|
||||
import auditWebhookStateSource from '../useAuditWebhookPanelState.ts?raw';
|
||||
import billingAdminPanelSource from '../BillingAdminPanel.tsx?raw';
|
||||
import generalSettingsPanelSource from '../GeneralSettingsPanel.tsx?raw';
|
||||
import aiSettingsPanelSource from '../AISettings.tsx?raw';
|
||||
@@ -59,6 +60,7 @@ const extractedModules = [
|
||||
'../infrastructureOperationsModel.tsx',
|
||||
'../useInfrastructureOperationsState.tsx',
|
||||
'../useAuditLogPanelState.ts',
|
||||
'../useAuditWebhookPanelState.ts',
|
||||
'../NodeModal.tsx',
|
||||
'../nodeModalModel.ts',
|
||||
'../useNodeModalState.ts',
|
||||
@@ -407,6 +409,19 @@ describe('Settings architecture guardrails', () => {
|
||||
expect(auditLogStateSource).toContain('trackPaywallViewed');
|
||||
});
|
||||
|
||||
it('keeps the audit webhook shell behind an extracted runtime owner', () => {
|
||||
expect(auditWebhookPanelSource).toContain('@/components/Settings/useAuditWebhookPanelState');
|
||||
expect(auditWebhookPanelSource).not.toContain('loadLicenseStatus();');
|
||||
expect(auditWebhookPanelSource).not.toContain('const fetchWebhooks = async () =>');
|
||||
expect(auditWebhookPanelSource).not.toContain('const saveWebhooks = async (urls: string[]) =>');
|
||||
expect(auditWebhookPanelSource).not.toContain('trackPaywallViewed');
|
||||
expect(auditWebhookStateSource).toContain('export const useAuditWebhookPanelState =');
|
||||
expect(auditWebhookStateSource).toContain('loadLicenseStatus();');
|
||||
expect(auditWebhookStateSource).toContain('const fetchWebhooks = async () =>');
|
||||
expect(auditWebhookStateSource).toContain('const saveWebhooks = async (urls: string[]) =>');
|
||||
expect(auditWebhookStateSource).toContain('trackPaywallViewed');
|
||||
});
|
||||
|
||||
it('keeps the SSO providers shell behind extracted runtime owners', () => {
|
||||
expect(ssoProvidersPanelSource).toContain('@/components/Settings/useSSOProvidersState');
|
||||
expect(ssoProvidersPanelSource).not.toContain('const loadProviders = async () =>');
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { createEffect, createSignal, onMount } from 'solid-js';
|
||||
import { apiFetchJSON } from '@/utils/apiClient';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { showSuccess, showWarning } from '@/utils/toast';
|
||||
import {
|
||||
entitlements,
|
||||
getUpgradeActionUrlOrFallback,
|
||||
hasFeature,
|
||||
licenseLoaded,
|
||||
loadLicenseStatus,
|
||||
startProTrial,
|
||||
} from '@/stores/license';
|
||||
import { trackPaywallViewed } from '@/utils/upgradeMetrics';
|
||||
import {
|
||||
getProTrialStartedMessage,
|
||||
getTrialAlreadyUsedMessage,
|
||||
getTrialStartErrorMessage,
|
||||
} from '@/utils/upgradePresentation';
|
||||
import {
|
||||
getAuditWebhookDuplicateUrlMessage,
|
||||
getAuditWebhookInvalidUrlMessage,
|
||||
getAuditWebhookSaveErrorMessage,
|
||||
getAuditWebhookSaveSuccessMessage,
|
||||
} from '@/utils/auditWebhookPresentation';
|
||||
|
||||
export const useAuditWebhookPanelState = (canManageOverride?: boolean) => {
|
||||
const [webhookUrls, setWebhookUrls] = createSignal<string[]>([]);
|
||||
const [newUrl, setNewUrl] = createSignal('');
|
||||
const [saving, setSaving] = createSignal(false);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
const [startingTrial, setStartingTrial] = createSignal(false);
|
||||
|
||||
const canManage = () => canManageOverride !== false;
|
||||
const canStartTrial = () => entitlements()?.trial_eligible !== false;
|
||||
const isAuditLoggingEnabled = () => hasFeature('audit_logging');
|
||||
const upgradeActionUrl = () => getUpgradeActionUrlOrFallback('audit_logging');
|
||||
|
||||
const handleStartTrial = async () => {
|
||||
if (startingTrial()) return;
|
||||
setStartingTrial(true);
|
||||
try {
|
||||
const result = await startProTrial();
|
||||
if (result?.outcome === 'redirect') {
|
||||
window.location.href = result.actionUrl;
|
||||
return;
|
||||
}
|
||||
showSuccess(getProTrialStartedMessage());
|
||||
} catch (error) {
|
||||
const statusCode = (error as { status?: number } | null)?.status;
|
||||
if (statusCode === 409) {
|
||||
showWarning(getTrialAlreadyUsedMessage());
|
||||
} else {
|
||||
showWarning(getTrialStartErrorMessage(error instanceof Error ? error.message : undefined));
|
||||
}
|
||||
} finally {
|
||||
setStartingTrial(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchWebhooks = async () => {
|
||||
try {
|
||||
const data = await apiFetchJSON<{ urls: string[] }>('/api/admin/webhooks/audit');
|
||||
setWebhookUrls(data.urls || []);
|
||||
} catch (error) {
|
||||
logger.error('[AuditWebhookPanel] Failed to fetch audit webhooks', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveWebhooks = async (urls: string[]) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await apiFetchJSON('/api/admin/webhooks/audit', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ urls }),
|
||||
});
|
||||
setWebhookUrls(urls);
|
||||
showSuccess(getAuditWebhookSaveSuccessMessage());
|
||||
} catch (error) {
|
||||
logger.error('[AuditWebhookPanel] Failed to save audit webhooks', error);
|
||||
showWarning(getAuditWebhookSaveErrorMessage());
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddWebhook = async () => {
|
||||
if (!canManage()) return;
|
||||
const url = newUrl().trim();
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
showWarning(getAuditWebhookInvalidUrlMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (webhookUrls().includes(url)) {
|
||||
showWarning(getAuditWebhookDuplicateUrlMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
await saveWebhooks([...webhookUrls(), url]);
|
||||
setNewUrl('');
|
||||
};
|
||||
|
||||
const handleRemoveWebhook = async (url: string) => {
|
||||
if (!canManage()) return;
|
||||
await saveWebhooks(webhookUrls().filter((existingUrl) => existingUrl !== url));
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
loadLicenseStatus();
|
||||
});
|
||||
|
||||
createEffect((wasPaywallVisible: boolean) => {
|
||||
const isPaywallVisible = licenseLoaded() && !hasFeature('audit_logging');
|
||||
if (isPaywallVisible && !wasPaywallVisible) {
|
||||
trackPaywallViewed('audit_logging', 'settings_audit_webhook_panel');
|
||||
}
|
||||
return isPaywallVisible;
|
||||
}, false);
|
||||
|
||||
createEffect(() => {
|
||||
if (hasFeature('audit_logging')) {
|
||||
void fetchWebhooks();
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
canManage,
|
||||
canStartTrial,
|
||||
handleAddWebhook,
|
||||
handleRemoveWebhook,
|
||||
handleStartTrial,
|
||||
isAuditLoggingEnabled,
|
||||
loading,
|
||||
newUrl,
|
||||
saving,
|
||||
setNewUrl,
|
||||
startingTrial,
|
||||
upgradeActionUrl,
|
||||
webhookUrls,
|
||||
};
|
||||
};
|
||||
@@ -290,6 +290,7 @@ const recoverySource = [
|
||||
].join('\n');
|
||||
import rolesPanelSource from '@/components/Settings/RolesPanel.tsx?raw';
|
||||
import auditWebhookPanelSource from '@/components/Settings/AuditWebhookPanel.tsx?raw';
|
||||
import auditWebhookStateSource from '@/components/Settings/useAuditWebhookPanelState.ts?raw';
|
||||
import auditWebhookPresentationSource from '@/utils/auditWebhookPresentation.ts?raw';
|
||||
import auditLogPanelSource from '@/components/Settings/AuditLogPanel.tsx?raw';
|
||||
import auditLogStateSource from '@/components/Settings/useAuditLogPanelState.ts?raw';
|
||||
@@ -2627,6 +2628,7 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(ssoProvidersModelSource).toContain('export const buildProviderPayload =');
|
||||
expect(ssoProvidersModelSource).toContain('export const buildProviderTestPayload =');
|
||||
expect(auditWebhookPanelSource).toContain('@/utils/auditWebhookPresentation');
|
||||
expect(auditWebhookPanelSource).toContain('@/components/Settings/useAuditWebhookPanelState');
|
||||
expect(auditWebhookPanelSource).toContain('getAuditWebhookFeatureGateCopy');
|
||||
expect(auditWebhookPanelSource).toContain('getAuditWebhookEmptyStateCopy');
|
||||
expect(auditWebhookPanelSource).toContain('getAuditWebhookLoadingState');
|
||||
@@ -2636,6 +2638,9 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(auditWebhookPanelSource).not.toContain('No audit webhooks configured yet.');
|
||||
expect(auditWebhookPanelSource).not.toContain('Loading audit webhooks…');
|
||||
expect(auditWebhookPanelSource).not.toContain('Audit Webhooks (Pro)');
|
||||
expect(auditWebhookPanelSource).not.toContain('loadLicenseStatus();');
|
||||
expect(auditWebhookPanelSource).not.toContain('const fetchWebhooks = async () =>');
|
||||
expect(auditWebhookPanelSource).not.toContain('const saveWebhooks = async (urls: string[]) =>');
|
||||
expect(auditWebhookPresentationSource).toContain(
|
||||
'export function getAuditWebhookFeatureGateCopy',
|
||||
);
|
||||
@@ -2643,6 +2648,25 @@ describe('frontend resource type boundaries', () => {
|
||||
'export function getAuditWebhookEmptyStateCopy',
|
||||
);
|
||||
expect(auditWebhookPresentationSource).toContain('export function getAuditWebhookLoadingState');
|
||||
expect(auditWebhookPresentationSource).toContain(
|
||||
'export function getAuditWebhookInvalidUrlMessage',
|
||||
);
|
||||
expect(auditWebhookPresentationSource).toContain(
|
||||
'export function getAuditWebhookDuplicateUrlMessage',
|
||||
);
|
||||
expect(auditWebhookPresentationSource).toContain(
|
||||
'export function getAuditWebhookSaveSuccessMessage',
|
||||
);
|
||||
expect(auditWebhookPresentationSource).toContain(
|
||||
'export function getAuditWebhookSaveErrorMessage',
|
||||
);
|
||||
expect(auditWebhookPresentationSource).toContain('AUDIT_WEBHOOK_SECURITY_NOTE_TITLE');
|
||||
expect(auditWebhookPresentationSource).toContain('AUDIT_WEBHOOK_SECURITY_NOTE_BODY');
|
||||
expect(auditWebhookStateSource).toContain('export const useAuditWebhookPanelState =');
|
||||
expect(auditWebhookStateSource).toContain('loadLicenseStatus();');
|
||||
expect(auditWebhookStateSource).toContain('trackPaywallViewed');
|
||||
expect(auditWebhookStateSource).toContain('const fetchWebhooks = async () =>');
|
||||
expect(auditWebhookStateSource).toContain('const saveWebhooks = async (urls: string[]) =>');
|
||||
expect(auditLogPanelSource).toContain('getAuditLogLoadingState');
|
||||
expect(auditLogPanelSource).toContain('getAuditLogEmptyState');
|
||||
expect(auditLogPanelSource).toContain('@/components/Settings/useAuditLogPanelState');
|
||||
|
||||
@@ -7,6 +7,11 @@ export const AUDIT_WEBHOOK_ENDPOINT_CARD_CLASS =
|
||||
export const AUDIT_WEBHOOK_ENDPOINT_ICON_CLASS =
|
||||
'p-2 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-md shrink-0';
|
||||
|
||||
export const AUDIT_WEBHOOK_SECURITY_NOTE_TITLE = 'Security Note';
|
||||
|
||||
export const AUDIT_WEBHOOK_SECURITY_NOTE_BODY =
|
||||
'Audit webhooks are dispatched asynchronously to avoid blocking user operations. Endpoints should still verify source trust (for example via an ingest secret) before processing events.';
|
||||
|
||||
export interface AuditWebhookFeatureGateCopy {
|
||||
title: string;
|
||||
body: string;
|
||||
@@ -34,3 +39,19 @@ export function getAuditWebhookLoadingState() {
|
||||
text: 'Loading audit webhooks…',
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function getAuditWebhookInvalidUrlMessage(): string {
|
||||
return 'Please enter a valid URL';
|
||||
}
|
||||
|
||||
export function getAuditWebhookDuplicateUrlMessage(): string {
|
||||
return 'This URL is already configured';
|
||||
}
|
||||
|
||||
export function getAuditWebhookSaveSuccessMessage(): string {
|
||||
return 'Audit webhooks updated';
|
||||
}
|
||||
|
||||
export function getAuditWebhookSaveErrorMessage(): string {
|
||||
return 'Failed to save webhook configuration';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user