mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Incorporate batch-start published Audit Webhooks guidance
Change-source: pulse-maintainer
This commit is contained in:
@@ -5979,6 +5979,11 @@ canonical `SettingsPanel` shell, while
|
||||
license/paywall lifecycle, webhook fetch/save flow, validation, paywall
|
||||
tracking, and hidden-upgrade copy posture. The shell must not re-accumulate API
|
||||
calls or paywall tracking inline.
|
||||
Because it is the only webhook page in Settings, the shell opens with a
|
||||
signpost, in both the gated and enabled states, saying audit webhooks carry
|
||||
security events only and linking to the Alerts notifications tab for Discord,
|
||||
Slack, ntfy or email delivery (issue #1344 tried to add a Discord webhook
|
||||
here). The link is a plain anchor the app Router intercepts.
|
||||
The same paid-runtime-required route applies to Audit Webhooks: missing
|
||||
`audit_logging` caused by a community runtime must keep the panel reachable,
|
||||
hide normal upgrade-plan prompts, and present the private Pulse Pro runtime
|
||||
|
||||
@@ -1329,6 +1329,10 @@ failure. If `runtime-capabilities` blocks `audit_logging` with
|
||||
active Pro license needs the private Pulse Pro runtime, but they must not
|
||||
expose license keys, billing identity, or plan-upgrade copy as part of that
|
||||
security/privacy feature gate.
|
||||
The Audit Webhooks panel also states its scope before any gate or endpoint
|
||||
list: these webhooks carry security events only, and alert delivery lives in
|
||||
alert notifications (`getAuditWebhookNotificationsSignpost`), so an operator
|
||||
looking for Discord or Slack alerts is not left configuring an audit sink.
|
||||
Audit-log storage availability is also a security/privacy trust boundary.
|
||||
The `pkg/audit/` runtime package owns persistent audit-store classification:
|
||||
transient SQLite busy/locked conditions must be retried and surfaced as
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "a28b1056459b2ae0fe01bbbe7aebd4c6ca7d3738",
|
||||
"verified_at": "2026-10-05T06:23:30Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Settings/AuditWebhookPanel.tsx",
|
||||
"frontend-modern/src/utils/auditWebhookPresentation.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Settings/AuditWebhookPanel.tsx": "416aa81cc2a011b937f9ad1d85174db72020c61e2d0f3a6bcc0d2eee99419457",
|
||||
"frontend-modern/src/utils/auditWebhookPresentation.ts": "f94b530c79dddedec64ecf4a9f5c80fe017580ea73ebcc479418c2bafaed3fba"
|
||||
},
|
||||
"routes": [
|
||||
"/settings/security-webhooks",
|
||||
"/alerts/notifications"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Audit Webhooks at 1440 with audit delivery not enabled on this instance: the panel opens with These carry security events only. To send alerts to Discord, Slack, ntfy or email, set up alert notifications, above the existing gate card. Before, the page gave no hint that alert webhooks live elsewhere",
|
||||
"Phone at 390: the signpost wraps over two lines above the gate card",
|
||||
"The enabled state (endpoint list) is not reachable on this mock instance, so its placement is covered by the settings architecture test"
|
||||
],
|
||||
"interactions": [
|
||||
"Clicked alert notifications and landed on /alerts/notifications without a full page reload"
|
||||
]
|
||||
}
|
||||
@@ -19,13 +19,33 @@ import {
|
||||
getAuditWebhookEmptyStateCopy,
|
||||
getAuditWebhookFeatureGateCopy,
|
||||
getAuditWebhookLoadingState,
|
||||
getAuditWebhookNotificationsSignpost,
|
||||
} from '@/utils/auditWebhookPresentation';
|
||||
import { pathForTab } from '@/features/alerts/types';
|
||||
import { useAuditWebhookPanelState } from '@/components/Settings/useAuditWebhookPanelState';
|
||||
|
||||
interface AuditWebhookPanelProps {
|
||||
canManage?: boolean;
|
||||
}
|
||||
|
||||
const AuditWebhookNotificationsSignpost: Component = () => {
|
||||
// A plain anchor: the app Router intercepts same-origin links, and the
|
||||
// panel also renders outside a Router in its read-only tests.
|
||||
const copy = getAuditWebhookNotificationsSignpost();
|
||||
return (
|
||||
<p class="text-sm text-muted leading-relaxed" data-audit-webhook-notifications-signpost>
|
||||
{copy.text}{' '}
|
||||
<a
|
||||
href={pathForTab('destinations')}
|
||||
class="font-medium text-blue-600 hover:underline dark:text-blue-400"
|
||||
>
|
||||
{copy.linkLabel}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
const [urlToRemove, setUrlToRemove] = createSignal<string | null>(null);
|
||||
let newUrlInputRef: HTMLInputElement | undefined;
|
||||
@@ -56,7 +76,8 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
return (
|
||||
<SettingsPanel title="Audit Webhooks">
|
||||
<Show when={!loading()} fallback={<div class="text-sm text-muted">Loading...</div>}>
|
||||
<Card tone="info" padding="md">
|
||||
<AuditWebhookNotificationsSignpost />
|
||||
<Card tone="info" padding="md" class="mt-3">
|
||||
<FeatureGateSection
|
||||
title={featureGateCopy().title}
|
||||
body={featureGateCopy().body}
|
||||
@@ -85,6 +106,7 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
Pulse can send a signed event payload whenever security-relevant activity occurs
|
||||
(logins, settings changes, RBAC updates, and similar audit events).
|
||||
</p>
|
||||
<AuditWebhookNotificationsSignpost />
|
||||
|
||||
<Show
|
||||
when={!loading()}
|
||||
|
||||
@@ -557,6 +557,13 @@ describe('settings architecture guardrails', () => {
|
||||
expect(auditWebhookPanelSource).toContain('getAuditWebhookFeatureGateCopy({');
|
||||
expect(auditWebhookPanelSource).toContain('paidRuntimeRequired: paidRuntimeRequired()');
|
||||
expect(auditWebhookPanelSource).not.toContain('Audit Webhooks (Pro)');
|
||||
// The only webhook page in Settings says it is not alert delivery and
|
||||
// links to alert notifications, in both the gated and enabled states.
|
||||
expect(auditWebhookPanelSource).toContain('getAuditWebhookNotificationsSignpost()');
|
||||
expect(auditWebhookPanelSource).toContain("pathForTab('destinations')");
|
||||
expect(auditWebhookPanelSource.match(/<AuditWebhookNotificationsSignpost \/>/g)).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(reportingPanelSource).toContain('if (!catalog || !state || showUpgradePrompts())');
|
||||
expect(reportingPanelSource).toContain('title: `${state.title} unavailable`');
|
||||
expect(reportingPanelSource).toContain(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
getAuditWebhookNotificationsSignpost,
|
||||
AUDIT_WEBHOOK_ENDPOINT_CARD_CLASS,
|
||||
AUDIT_WEBHOOK_ENDPOINT_ICON_CLASS,
|
||||
AUDIT_WEBHOOK_READONLY_NOTICE_CLASS,
|
||||
@@ -42,4 +43,12 @@ describe('auditWebhookPresentation', () => {
|
||||
expect(AUDIT_WEBHOOK_ENDPOINT_CARD_CLASS).toContain('bg-surface-alt');
|
||||
expect(AUDIT_WEBHOOK_ENDPOINT_ICON_CLASS).toContain('bg-blue-100');
|
||||
});
|
||||
|
||||
it('points people looking for alert delivery to alert notifications', () => {
|
||||
const signpost = getAuditWebhookNotificationsSignpost();
|
||||
expect(signpost.text).toContain('security events only');
|
||||
expect(signpost.text).toContain('Discord');
|
||||
expect(signpost.linkLabel).toBe('alert notifications');
|
||||
expect(`${signpost.text} ${signpost.linkLabel}`).not.toContain(';');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,6 +51,16 @@ export function getAuditWebhookFeatureGateCopy(
|
||||
};
|
||||
}
|
||||
|
||||
// People looking for alert delivery land here first because it is the only
|
||||
// webhook page in Settings (issue #1344 tried to add a Discord webhook from
|
||||
// it). The panel says what it is for and where alert notifications live.
|
||||
export function getAuditWebhookNotificationsSignpost() {
|
||||
return {
|
||||
text: 'These carry security events only. To send alerts to Discord, Slack, ntfy or email, set up',
|
||||
linkLabel: 'alert notifications',
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function getAuditWebhookEmptyStateCopy(): AuditWebhookEmptyStateCopy {
|
||||
return {
|
||||
title: 'No audit webhooks configured yet.',
|
||||
|
||||
Reference in New Issue
Block a user