feat: location filter for global audit log (#662)

This commit is contained in:
Kyle Mendell
2025-06-19 12:12:53 -05:00
committed by GitHub
parent 481df3bcb9
commit ac5a121f66
11 changed files with 48 additions and 8 deletions

View File

@@ -89,6 +89,7 @@ func (alc *AuditLogController) listAuditLogsForUserHandler(c *gin.Context) {
// @Param filters[userId] query string false "Filter by user ID" // @Param filters[userId] query string false "Filter by user ID"
// @Param filters[event] query string false "Filter by event type" // @Param filters[event] query string false "Filter by event type"
// @Param filters[clientName] query string false "Filter by client name" // @Param filters[clientName] query string false "Filter by client name"
// @Param filters[location] query string false "Filter by location type (external or internal)"
// @Success 200 {object} dto.Paginated[dto.AuditLogDto] // @Success 200 {object} dto.Paginated[dto.AuditLogDto]
// @Router /api/audit-logs/all [get] // @Router /api/audit-logs/all [get]
func (alc *AuditLogController) listAllAuditLogsHandler(c *gin.Context) { func (alc *AuditLogController) listAllAuditLogsHandler(c *gin.Context) {

View File

@@ -23,4 +23,5 @@ type AuditLogFilterDto struct {
UserID string `form:"filters[userId]"` UserID string `form:"filters[userId]"`
Event string `form:"filters[event]"` Event string `form:"filters[event]"`
ClientName string `form:"filters[clientName]"` ClientName string `form:"filters[clientName]"`
Location string `form:"filters[location]"`
} }

View File

@@ -150,6 +150,14 @@ func (s *AuditLogService) ListAllAuditLogs(ctx context.Context, sortedPagination
return nil, utils.PaginationResponse{}, fmt.Errorf("unsupported database dialect: %s", dialect) return nil, utils.PaginationResponse{}, fmt.Errorf("unsupported database dialect: %s", dialect)
} }
} }
if filters.Location != "" {
switch filters.Location {
case "external":
query = query.Where("country != 'Internal Network'")
case "internal":
query = query.Where("country = 'Internal Network'")
}
}
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs) pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs)
if err != nil { if err != nil {

View File

@@ -0,0 +1 @@
DROP INDEX idx_audit_logs_country;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_audit_logs_country ON audit_logs(country);

View File

@@ -0,0 +1 @@
DROP INDEX idx_audit_logs_country;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_audit_logs_country ON audit_logs(country);

View File

@@ -320,6 +320,7 @@
"all_users": "All Users", "all_users": "All Users",
"all_events": "All Events", "all_events": "All Events",
"all_clients": "All Clients", "all_clients": "All Clients",
"all_locations": "All Locations",
"global_audit_log": "Global Audit Log", "global_audit_log": "Global Audit Log",
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.", "see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
"token_sign_in": "Token Sign In", "token_sign_in": "Token Sign In",

View File

@@ -14,6 +14,7 @@
onSelect, onSelect,
oninput, oninput,
isLoading, isLoading,
disableSearch = false,
selectText = m.select_an_option(), selectText = m.select_an_option(),
...restProps ...restProps
}: HTMLAttributes<HTMLButtonElement> & { }: HTMLAttributes<HTMLButtonElement> & {
@@ -25,6 +26,7 @@
oninput?: FormEventHandler<HTMLInputElement>; oninput?: FormEventHandler<HTMLInputElement>;
onSelect?: (value: string) => void; onSelect?: (value: string) => void;
isLoading?: boolean; isLoading?: boolean;
disableSearch?: boolean;
selectText?: string; selectText?: string;
} = $props(); } = $props();
@@ -76,13 +78,15 @@
</Popover.Trigger> </Popover.Trigger>
<Popover.Content class="p-0" sameWidth> <Popover.Content class="p-0" sameWidth>
<Command.Root shouldFilter={false}> <Command.Root shouldFilter={false}>
<Command.Input {#if !disableSearch}
placeholder={m.search()} <Command.Input
oninput={(e) => { placeholder={m.search()}
filterItems(e.currentTarget.value); oninput={(e) => {
oninput?.(e); filterItems(e.currentTarget.value);
}} oninput?.(e);
/> }}
/>
{/if}
<Command.Empty> <Command.Empty>
{#if isLoading} {#if isLoading}
<div class="flex w-full justify-center"> <div class="flex w-full justify-center">

View File

@@ -14,5 +14,6 @@ export type AuditLog = {
export type AuditLogFilter = { export type AuditLogFilter = {
userId: string; userId: string;
event: string; event: string;
location: string;
clientName: string; clientName: string;
}; };

View File

@@ -18,9 +18,15 @@
let filters: AuditLogFilter = $state({ let filters: AuditLogFilter = $state({
userId: '', userId: '',
event: '', event: '',
location: '',
clientName: '' clientName: ''
}); });
const locationTypes = $state({
external: 'External Networks',
internal: 'Internal Networks'
});
const eventTypes = $state({ const eventTypes = $state({
SIGN_IN: m.sign_in(), SIGN_IN: m.sign_in(),
TOKEN_SIGN_IN: m.token_sign_in(), TOKEN_SIGN_IN: m.token_sign_in(),
@@ -47,7 +53,7 @@
> >
</Card.Header> </Card.Header>
<Card.Content> <Card.Content>
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-3"> <div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-4">
<div> <div>
{#await auditLogService.listUsers()} {#await auditLogService.listUsers()}
<Select.Root type="single"> <Select.Root type="single">
@@ -82,6 +88,20 @@
bind:value={filters.event} bind:value={filters.event}
/> />
</div> </div>
<div>
<SearchableSelect
disableSearch={true}
class="w-full"
items={[
{ value: '', label: m.all_locations() },
...Object.entries(locationTypes).map(([value, label]) => ({
value,
label
}))
]}
bind:value={filters.location}
/>
</div>
<div> <div>
{#await auditLogService.listClientNames()} {#await auditLogService.listClientNames()}
<Select.Root <Select.Root