Skip to content

Commit 18e6847

Browse files
feat: add entity search, copyable IDs, audit improvements, and policy enforcement
- Add EntitySelect combobox component with debounced search (Headless UI) - Add CopyableId component for truncated UUID display with click-to-copy - Add searchable entity filters to AdminAuditPage and QueryAuditPage - Add useDebounce hook and entitySearchFns utility - Add tests for CopyableId, EntitySelect, AdminAuditPage, QueryAuditPage - Add CopyableId to list pages (users, roles, policies, datasources, attributes) - Add AuditTimeline to DataSourceEditPage and RoleEditPage - Fix audit resource_type to use "proxy_user" (matching DB table name) - Fix update_datasource to skip no-op updates and add PolicyHook cache invalidation after datasource mutations - Add policy enforcement integration tests and test helpers - Update permission-system and security-vectors docs - Update proxy CLAUDE.md with new patterns and conventions
1 parent 8cc217a commit 18e6847

37 files changed

Lines changed: 2847 additions & 324 deletions

admin-ui/package-lock.json

Lines changed: 239 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

admin-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@codemirror/lang-javascript": "^6.2.5",
1717
"@codemirror/lang-json": "^6.0.2",
1818
"@codemirror/lint": "^6.9.5",
19+
"@headlessui/react": "^2.2.10",
1920
"@microsoft/fetch-event-source": "^2.0.1",
2021
"@tanstack/react-query": "^5.66.0",
2122
"@uiw/react-codemirror": "^4.25.9",

admin-ui/src/components/AttributeDefinitionForm.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,46 @@ export function AttributeDefinitionForm({
179179
<label className="block text-sm font-medium text-gray-700 mb-1">
180180
Default value
181181
</label>
182-
<input
183-
type="text"
184-
value={defaultValue}
185-
onChange={(e) => setDefaultValue(e.target.value)}
186-
placeholder={valueType === 'list' ? 'JSON array, e.g., ["default"]' : 'Optional'}
187-
className={inputCls}
188-
/>
182+
{valueType === 'boolean' ? (
183+
<select
184+
value={defaultValue}
185+
onChange={(e) => setDefaultValue(e.target.value)}
186+
className={inputCls}
187+
>
188+
<option value="">No default (null)</option>
189+
<option value="true">true</option>
190+
<option value="false">false</option>
191+
</select>
192+
) : (
193+
<div className="flex items-center gap-2">
194+
<input
195+
type={valueType === 'integer' ? 'number' : 'text'}
196+
value={defaultValue}
197+
onChange={(e) => setDefaultValue(e.target.value)}
198+
placeholder={
199+
valueType === 'list'
200+
? 'No default (null) — or JSON array, e.g., ["default"]'
201+
: 'No default (null)'
202+
}
203+
className={`${inputCls} flex-1`}
204+
/>
205+
{defaultValue && (
206+
<button
207+
type="button"
208+
onClick={() => setDefaultValue('')}
209+
className="text-gray-400 hover:text-red-500 text-sm px-1"
210+
title="Clear to null"
211+
>
212+
&times;
213+
</button>
214+
)}
215+
</div>
216+
)}
217+
<p className="text-xs text-gray-400 mt-1">
218+
{defaultValue
219+
? `Users without this attribute will be treated as having the value "${defaultValue}" when policies are evaluated. This value is applied by the proxy at query time — it is not stored on the user.`
220+
: 'When null: users without this attribute will have NULL substituted in policy expressions. In SQL, comparisons with NULL (e.g., tenant = NULL) evaluate to NULL, which is treated as false — so equality filters return zero rows. This is applied by the proxy at query time, not stored on the user.'}
221+
</p>
189222
</div>
190223

191224
<div>

0 commit comments

Comments
 (0)