@burdenoff/microfe-vibecontrols 2026.727.1 → 2026.727.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -51,7 +51,7 @@ function S() {
|
|
|
51
51
|
}), pe = (fe?.vibes?.items ?? []).map((e) => ({
|
|
52
52
|
id: e.id,
|
|
53
53
|
name: e.name
|
|
54
|
-
})),
|
|
54
|
+
})), { data: Q } = e({
|
|
55
55
|
variables: { id: U },
|
|
56
56
|
skip: !U
|
|
57
57
|
});
|
|
@@ -61,7 +61,7 @@ function S() {
|
|
|
61
61
|
}, 300);
|
|
62
62
|
return () => window.clearTimeout(e);
|
|
63
63
|
}, [E]);
|
|
64
|
-
let
|
|
64
|
+
let me = (Y?.vibeAuditLogs.items ?? []).slice(z * x, (z + 1) * x), $ = Y?.vibeAuditLogs.total ?? 0;
|
|
65
65
|
return w ? /* @__PURE__ */ _("div", {
|
|
66
66
|
className: "mx-auto max-w-[1400px] p-4 sm:p-6",
|
|
67
67
|
children: /* @__PURE__ */ _(a, { message: G("audit.loadingPermissions", "Loading permissions...") })
|
|
@@ -85,7 +85,7 @@ function S() {
|
|
|
85
85
|
children: [/* @__PURE__ */ _(h, {
|
|
86
86
|
size: 14,
|
|
87
87
|
className: "mt-0.5 flex-shrink-0"
|
|
88
|
-
}), /* @__PURE__ */ _("span", { children: G("audit.retentionBanner",
|
|
88
|
+
}), /* @__PURE__ */ _("span", { children: G("audit.retentionBanner", "Audit logs are retained for 7 years for compliance and cannot be deleted.") })]
|
|
89
89
|
}),
|
|
90
90
|
V ? /* @__PURE__ */ v("div", {
|
|
91
91
|
className: "mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-[var(--color-accent-soft)] px-3 py-2 text-xs text-text-primary",
|
|
@@ -159,7 +159,7 @@ function S() {
|
|
|
159
159
|
}) : /* @__PURE__ */ _("div", {
|
|
160
160
|
"data-tour": "audit-log-table",
|
|
161
161
|
children: /* @__PURE__ */ _(u, {
|
|
162
|
-
items:
|
|
162
|
+
items: me,
|
|
163
163
|
total: $,
|
|
164
164
|
hasMore: Y?.vibeAuditLogs.hasMore ?? !1,
|
|
165
165
|
page: z,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeAuditPage.js","names":[],"sources":["../../src/pages/VibeAuditPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { Shield } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuditExportButton,\n AuditLogDetail,\n AuditLogFilters,\n AuditLogStats,\n AuditLogTable,\n} from '@/components/audit';\nimport {\n EmptyState,\n ErrorState,\n GetHelpButton,\n LoadingState,\n PageHeader,\n} from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n useGetVibeAuditLogQuery,\n useListAgentsQuery,\n useListVibesQuery,\n useVibeAuditDashboardQuery,\n} from '@/generated/wspace-operations';\nimport type {\n VibeAuditAction,\n VibeAuditLogFilterInput,\n VibeAuditResourceType,\n VibeAuditStatus,\n} from '@/generated/wspace-types';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nconst PAGE_SIZE = 50;\n\nexport function VibeAuditPage() {\n useVibeControls();\n\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');\n const [actionFilter, setActionFilter] = useState<string | null>('all');\n const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>('all');\n const [statusFilter, setStatusFilter] = useState<string | null>('all');\n const [agentFilter, setAgentFilter] = useState<string | null>('all');\n const [vibeFilter, setVibeFilter] = useState<string | null>('all');\n const [hasCommandFilter, setHasCommandFilter] = useState(false);\n const [page, setPage] = useState(0);\n // F2 — session-history drill-down. Initialised from a ?sessionId= URL param\n // (reciprocal link from the in-session History tab) and also settable by\n // clicking \"Show only this session\" on a SESSION-typed audit entry.\n const [sessionDrillId, setSessionDrillId] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get('sessionId');\n });\n const [selectedLogId, setSelectedLogId] = useState<string | null>(null);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const canListAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_LIST);\n const canExportAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_EXPORT);\n const shouldSkipQueries = permissionsLoading || !canListAudit;\n\n const filter: VibeAuditLogFilterInput = {};\n if (debouncedSearchQuery) filter.search = debouncedSearchQuery;\n if (actionFilter && actionFilter !== 'all') filter.action = actionFilter as VibeAuditAction;\n if (resourceTypeFilter && resourceTypeFilter !== 'all')\n filter.resourceType = resourceTypeFilter as VibeAuditResourceType;\n if (statusFilter && statusFilter !== 'all') filter.status = statusFilter as VibeAuditStatus;\n if (hasCommandFilter) filter.hasCommand = true;\n // Agent / Vibe filters: backend exposes `resourceId` + `resourceType`.\n // We narrow `resourceType` accordingly so the server returns only audit\n // rows whose target is the chosen agent / vibe. If the user already\n // chose a resourceType that conflicts (e.g. SESSION), the resourceId\n // filter would over-narrow — but that combination is documented as\n // intentional power-user behaviour.\n if (agentFilter && agentFilter !== 'all') {\n filter.resourceId = agentFilter;\n if (!filter.resourceType) filter.resourceType = 'AGENT' as VibeAuditResourceType;\n } else if (vibeFilter && vibeFilter !== 'all') {\n filter.resourceId = vibeFilter;\n if (!filter.resourceType) filter.resourceType = 'VIBE' as VibeAuditResourceType;\n }\n // Session drill-down — the server matches resourceType=SESSION AND\n // resourceId=sessionId. Works for killed (soft-deleted) sessions too.\n if (sessionDrillId) filter.sessionId = sessionDrillId;\n\n const { data, loading, error, refetch } = useVibeAuditDashboardQuery({\n variables: {\n filter,\n pagination: { limit: Math.min(Math.max((page + 1) * PAGE_SIZE, PAGE_SIZE), 100) },\n },\n skip: shouldSkipQueries,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const agentsList = (agentsData?.vibecontrolsAgents ?? []).map(\n (a: { id: string; name: string }) => ({ id: a.id, name: a.name })\n );\n\n const { data: vibesData } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const vibesList = (vibesData?.vibes?.items ?? []).map((v: { id: string; name: string }) => ({\n id: v.id,\n name: v.name,\n }));\n\n // BOFF-5164: retentionPolicy.retentionDays now always reports the\n // mandatory 7-year (2555-day) compliance floor — it is no longer varied\n // by plan tier, and audit logs can never be deleted before then (see\n // wspace-vibecontrols-svc audit.gql / src/jobs/audit-retention-job.ts).\n const retentionDays = data?.vibeAuditLogStats?.retentionPolicy?.retentionDays ?? 2555;\n\n const { data: detailData } = useGetVibeAuditLogQuery({\n variables: { id: selectedLogId! },\n skip: !selectedLogId,\n });\n\n useEffect(() => {\n const timeoutId = window.setTimeout(() => {\n setDebouncedSearchQuery(searchQuery);\n setPage(0);\n }, 300);\n\n return () => window.clearTimeout(timeoutId);\n }, [searchQuery]);\n\n const allItems = data?.vibeAuditLogs.items ?? [];\n const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);\n const totalAuditItems = data?.vibeAuditLogs.total ?? 0;\n\n if (permissionsLoading) {\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <LoadingState message={tr('audit.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAudit) {\n return (\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'common.accessDeniedDescription',\n \"You don't have permission to access this resource.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.vibeAudit', 'Audit')}\n description={tr(\n 'audit.description',\n 'Comprehensive audit trail of all VibeControls operations'\n )}\n actions={canExportAudit ? <AuditExportButton filter={filter} /> : undefined}\n />\n\n <PagePurpose icon={<Shield className=\"size-4\" />}>\n {tr(\n 'audit.purpose',\n 'The audit trail records who did what across VibeControls — every agent registered, session started, setting changed and more — with timestamps and actors. Use it to investigate an incident, satisfy a compliance review, or simply understand a change after the fact. Filter and export the log for evidence.'\n )}\n </PagePurpose>\n\n {/* Retention banner — BOFF-5164: audit logs are immutable and retained\n for the platform's mandatory 7-year SOC2 compliance window; they\n can never be deleted before then (no admin delete capability\n exists on this resource). `retentionDays` is sourced from the\n backend `retentionPolicy` so this stays truthful if the mandatory\n floor is ever legally extended further. */}\n <div className=\"mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg-subtle px-3 py-2 text-xs text-status-info-text\">\n <Shield size={14} className=\"mt-0.5 flex-shrink-0\" />\n <span>\n {tr(\n 'audit.retentionBanner',\n `Audit logs are retained for ${Math.round(retentionDays / 365)} years for compliance and cannot be deleted.`\n )}\n </span>\n </div>\n\n {/* Session drill-down banner — shown when scoped to one session's activity. */}\n {sessionDrillId ? (\n <div className=\"mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-[var(--color-accent-soft)] px-3 py-2 text-xs text-text-primary\">\n <span className=\"truncate\">\n {tr('audit.sessionScopeBanner', 'Showing activity for session')}{' '}\n <span className=\"font-mono\">{sessionDrillId}</span>\n </span>\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(null);\n setPage(0);\n }}\n className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-1 font-medium text-text-secondary hover:text-text-primary\"\n >\n {tr('audit.clearSessionScope', 'Clear')}\n </button>\n </div>\n ) : null}\n\n {/* AuditLogStats is a custom component that doesn't forward rest props\n onto a DOM node, so the tour anchor lives on a wrapper <div>. */}\n <div data-tour=\"audit-log-stats\">\n <AuditLogStats stats={data?.vibeAuditLogStats} loading={loading && !data} />\n </div>\n\n <AuditLogFilters\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n actionFilter={actionFilter}\n onActionChange={(value) => {\n setActionFilter(value);\n setPage(0);\n }}\n resourceTypeFilter={resourceTypeFilter}\n onResourceTypeChange={(value) => {\n setResourceTypeFilter(value);\n setPage(0);\n }}\n statusFilter={statusFilter}\n onStatusChange={(value) => {\n setStatusFilter(value);\n setPage(0);\n }}\n hasCommandFilter={hasCommandFilter}\n onHasCommandChange={(value) => {\n setHasCommandFilter(value);\n setPage(0);\n }}\n agentFilter={agentFilter}\n onAgentChange={(value) => {\n setAgentFilter(value);\n // Agent + Vibe are mutually exclusive via resourceId — clearing one when\n // the other is set keeps the resolved filter input coherent.\n if (value !== 'all') setVibeFilter('all');\n setPage(0);\n }}\n agents={agentsList}\n vibeFilter={vibeFilter}\n onVibeChange={(value) => {\n setVibeFilter(value);\n if (value !== 'all') setAgentFilter('all');\n setPage(0);\n }}\n vibes={vibesList}\n />\n\n {loading && !data ? (\n <LoadingState message={tr('audit.loadingLogs', 'Loading audit logs...')} />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => refetch()} />\n ) : totalAuditItems === 0 ? (\n <EmptyState\n icon={<Shield size={48} className=\"text-text-secondary\" />}\n title={tr('audit.noLogs', 'No audit logs')}\n description={tr(\n 'audit.emptyDescription',\n 'Audit logs will appear here as operations are performed in VibeControls.'\n )}\n action={\n <GetHelpButton prompt=\"What is the Audit Log in VibeControls and how do I use it?\" />\n }\n />\n ) : (\n <div data-tour=\"audit-log-table\">\n <AuditLogTable\n items={items}\n total={totalAuditItems}\n hasMore={data?.vibeAuditLogs.hasMore ?? false}\n page={page}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n onSelectLog={setSelectedLogId}\n selectedLogId={selectedLogId}\n />\n </div>\n )}\n\n {selectedLogId && detailData?.vibeAuditLog ? (\n <AuditLogDetail\n log={detailData.vibeAuditLog}\n onClose={() => setSelectedLogId(null)}\n footer={\n detailData.vibeAuditLog.resourceType === 'SESSION' &&\n detailData.vibeAuditLog.resourceId &&\n detailData.vibeAuditLog.resourceId !== sessionDrillId ? (\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(detailData.vibeAuditLog?.resourceId ?? null);\n setSelectedLogId(null);\n setPage(0);\n }}\n className=\"w-full rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n {tr('audit.showOnlyThisSession', \"Show only this session's activity\")}\n </button>\n ) : null\n }\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeAuditPage.js","names":[],"sources":["../../src/pages/VibeAuditPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { Shield } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuditExportButton,\n AuditLogDetail,\n AuditLogFilters,\n AuditLogStats,\n AuditLogTable,\n} from '@/components/audit';\nimport {\n EmptyState,\n ErrorState,\n GetHelpButton,\n LoadingState,\n PageHeader,\n} from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n useGetVibeAuditLogQuery,\n useListAgentsQuery,\n useListVibesQuery,\n useVibeAuditDashboardQuery,\n} from '@/generated/wspace-operations';\nimport type {\n VibeAuditAction,\n VibeAuditLogFilterInput,\n VibeAuditResourceType,\n VibeAuditStatus,\n} from '@/generated/wspace-types';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nconst PAGE_SIZE = 50;\n\nexport function VibeAuditPage() {\n useVibeControls();\n\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');\n const [actionFilter, setActionFilter] = useState<string | null>('all');\n const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>('all');\n const [statusFilter, setStatusFilter] = useState<string | null>('all');\n const [agentFilter, setAgentFilter] = useState<string | null>('all');\n const [vibeFilter, setVibeFilter] = useState<string | null>('all');\n const [hasCommandFilter, setHasCommandFilter] = useState(false);\n const [page, setPage] = useState(0);\n // F2 — session-history drill-down. Initialised from a ?sessionId= URL param\n // (reciprocal link from the in-session History tab) and also settable by\n // clicking \"Show only this session\" on a SESSION-typed audit entry.\n const [sessionDrillId, setSessionDrillId] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get('sessionId');\n });\n const [selectedLogId, setSelectedLogId] = useState<string | null>(null);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const canListAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_LIST);\n const canExportAudit = hasPermission(VIBE_PERMISSIONS.AUDIT_EXPORT);\n const shouldSkipQueries = permissionsLoading || !canListAudit;\n\n const filter: VibeAuditLogFilterInput = {};\n if (debouncedSearchQuery) filter.search = debouncedSearchQuery;\n if (actionFilter && actionFilter !== 'all') filter.action = actionFilter as VibeAuditAction;\n if (resourceTypeFilter && resourceTypeFilter !== 'all')\n filter.resourceType = resourceTypeFilter as VibeAuditResourceType;\n if (statusFilter && statusFilter !== 'all') filter.status = statusFilter as VibeAuditStatus;\n if (hasCommandFilter) filter.hasCommand = true;\n // Agent / Vibe filters: backend exposes `resourceId` + `resourceType`.\n // We narrow `resourceType` accordingly so the server returns only audit\n // rows whose target is the chosen agent / vibe. If the user already\n // chose a resourceType that conflicts (e.g. SESSION), the resourceId\n // filter would over-narrow — but that combination is documented as\n // intentional power-user behaviour.\n if (agentFilter && agentFilter !== 'all') {\n filter.resourceId = agentFilter;\n if (!filter.resourceType) filter.resourceType = 'AGENT' as VibeAuditResourceType;\n } else if (vibeFilter && vibeFilter !== 'all') {\n filter.resourceId = vibeFilter;\n if (!filter.resourceType) filter.resourceType = 'VIBE' as VibeAuditResourceType;\n }\n // Session drill-down — the server matches resourceType=SESSION AND\n // resourceId=sessionId. Works for killed (soft-deleted) sessions too.\n if (sessionDrillId) filter.sessionId = sessionDrillId;\n\n const { data, loading, error, refetch } = useVibeAuditDashboardQuery({\n variables: {\n filter,\n pagination: { limit: Math.min(Math.max((page + 1) * PAGE_SIZE, PAGE_SIZE), 100) },\n },\n skip: shouldSkipQueries,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const agentsList = (agentsData?.vibecontrolsAgents ?? []).map(\n (a: { id: string; name: string }) => ({ id: a.id, name: a.name })\n );\n\n const { data: vibesData } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n });\n const vibesList = (vibesData?.vibes?.items ?? []).map((v: { id: string; name: string }) => ({\n id: v.id,\n name: v.name,\n }));\n\n const { data: detailData } = useGetVibeAuditLogQuery({\n variables: { id: selectedLogId! },\n skip: !selectedLogId,\n });\n\n useEffect(() => {\n const timeoutId = window.setTimeout(() => {\n setDebouncedSearchQuery(searchQuery);\n setPage(0);\n }, 300);\n\n return () => window.clearTimeout(timeoutId);\n }, [searchQuery]);\n\n const allItems = data?.vibeAuditLogs.items ?? [];\n const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);\n const totalAuditItems = data?.vibeAuditLogs.total ?? 0;\n\n if (permissionsLoading) {\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <LoadingState message={tr('audit.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAudit) {\n return (\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'common.accessDeniedDescription',\n \"You don't have permission to access this resource.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto max-w-[1400px] p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.vibeAudit', 'Audit')}\n description={tr(\n 'audit.description',\n 'Comprehensive audit trail of all VibeControls operations'\n )}\n actions={canExportAudit ? <AuditExportButton filter={filter} /> : undefined}\n />\n\n <PagePurpose icon={<Shield className=\"size-4\" />}>\n {tr(\n 'audit.purpose',\n 'The audit trail records who did what across VibeControls — every agent registered, session started, setting changed and more — with timestamps and actors. Use it to investigate an incident, satisfy a compliance review, or simply understand a change after the fact. Filter and export the log for evidence.'\n )}\n </PagePurpose>\n\n {/* Retention banner — BOFF-5164: audit logs are immutable and retained\n for the platform's mandatory 7-year SOC2 compliance window; they\n can never be deleted before then (no admin delete capability\n exists on this resource). Retention is now a flat mandatory floor\n for every workspace (no longer plan-tier-variable), so this is a\n plain string rather than an interpolated one — the i18n key\n extractor (scripts/extract-i18n-keys.ts in vibecontrols-app) can\n only capture a fallback from a no-substitution string/template\n literal; a `${...}`-interpolated one silently falls back to a\n title-cased placeholder (\"Retention Banner\") instead of real copy. */}\n <div className=\"mb-4 flex items-start gap-2 rounded-md border border-status-info-border bg-status-info-bg-subtle px-3 py-2 text-xs text-status-info-text\">\n <Shield size={14} className=\"mt-0.5 flex-shrink-0\" />\n <span>\n {tr(\n 'audit.retentionBanner',\n 'Audit logs are retained for 7 years for compliance and cannot be deleted.'\n )}\n </span>\n </div>\n\n {/* Session drill-down banner — shown when scoped to one session's activity. */}\n {sessionDrillId ? (\n <div className=\"mb-4 flex items-center justify-between gap-2 rounded-md border border-action-primary-border bg-[var(--color-accent-soft)] px-3 py-2 text-xs text-text-primary\">\n <span className=\"truncate\">\n {tr('audit.sessionScopeBanner', 'Showing activity for session')}{' '}\n <span className=\"font-mono\">{sessionDrillId}</span>\n </span>\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(null);\n setPage(0);\n }}\n className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-1 font-medium text-text-secondary hover:text-text-primary\"\n >\n {tr('audit.clearSessionScope', 'Clear')}\n </button>\n </div>\n ) : null}\n\n {/* AuditLogStats is a custom component that doesn't forward rest props\n onto a DOM node, so the tour anchor lives on a wrapper <div>. */}\n <div data-tour=\"audit-log-stats\">\n <AuditLogStats stats={data?.vibeAuditLogStats} loading={loading && !data} />\n </div>\n\n <AuditLogFilters\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n actionFilter={actionFilter}\n onActionChange={(value) => {\n setActionFilter(value);\n setPage(0);\n }}\n resourceTypeFilter={resourceTypeFilter}\n onResourceTypeChange={(value) => {\n setResourceTypeFilter(value);\n setPage(0);\n }}\n statusFilter={statusFilter}\n onStatusChange={(value) => {\n setStatusFilter(value);\n setPage(0);\n }}\n hasCommandFilter={hasCommandFilter}\n onHasCommandChange={(value) => {\n setHasCommandFilter(value);\n setPage(0);\n }}\n agentFilter={agentFilter}\n onAgentChange={(value) => {\n setAgentFilter(value);\n // Agent + Vibe are mutually exclusive via resourceId — clearing one when\n // the other is set keeps the resolved filter input coherent.\n if (value !== 'all') setVibeFilter('all');\n setPage(0);\n }}\n agents={agentsList}\n vibeFilter={vibeFilter}\n onVibeChange={(value) => {\n setVibeFilter(value);\n if (value !== 'all') setAgentFilter('all');\n setPage(0);\n }}\n vibes={vibesList}\n />\n\n {loading && !data ? (\n <LoadingState message={tr('audit.loadingLogs', 'Loading audit logs...')} />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => refetch()} />\n ) : totalAuditItems === 0 ? (\n <EmptyState\n icon={<Shield size={48} className=\"text-text-secondary\" />}\n title={tr('audit.noLogs', 'No audit logs')}\n description={tr(\n 'audit.emptyDescription',\n 'Audit logs will appear here as operations are performed in VibeControls.'\n )}\n action={\n <GetHelpButton prompt=\"What is the Audit Log in VibeControls and how do I use it?\" />\n }\n />\n ) : (\n <div data-tour=\"audit-log-table\">\n <AuditLogTable\n items={items}\n total={totalAuditItems}\n hasMore={data?.vibeAuditLogs.hasMore ?? false}\n page={page}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n onSelectLog={setSelectedLogId}\n selectedLogId={selectedLogId}\n />\n </div>\n )}\n\n {selectedLogId && detailData?.vibeAuditLog ? (\n <AuditLogDetail\n log={detailData.vibeAuditLog}\n onClose={() => setSelectedLogId(null)}\n footer={\n detailData.vibeAuditLog.resourceType === 'SESSION' &&\n detailData.vibeAuditLog.resourceId &&\n detailData.vibeAuditLog.resourceId !== sessionDrillId ? (\n <button\n type=\"button\"\n onClick={() => {\n setSessionDrillId(detailData.vibeAuditLog?.resourceId ?? null);\n setSelectedLogId(null);\n setPage(0);\n }}\n className=\"w-full rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n {tr('audit.showOnlyThisSession', \"Show only this session's activity\")}\n </button>\n ) : null\n }\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|