Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d08025a
Adding audit trail to dropdown under Welcome, user! under "manage acc…
Jun 10, 2025
4ae5c54
New audit trails page made
Jun 11, 2025
8d47648
Returning JSON for most recent session with username as input woring
Jun 16, 2025
6857f50
Working UI for recent session, good starting point
Jul 2, 2025
8c3e670
Added pop-up modal functionality for data column (now named Details),…
Jul 2, 2025
e6458ca
Merge remote-tracking branch 'origin/main' into task/WP-930
Jul 9, 2025
530138a
Added antd modal feature, data at bottom of modal, another good start…
Jul 9, 2025
8077e97
Merge main into task/WP-930
Jul 9, 2025
2287234
Initial push
Jul 15, 2025
e06ce9e
Removed comments
Jul 15, 2025
31f2134
Delete unnecessary add-ons, linting error fixes
Jul 15, 2025
4d1876b
Added extra lines to files, added hooks, minor changes to audittrail.…
Jul 22, 2025
7ddcc49
Update designsafe/apps/accounts/templates/designsafe/apps/accounts/ba…
erikriv16 Jul 22, 2025
12ce212
Merge branch 'main' into task/WP-930
fnets Jul 28, 2025
76f98e3
Fixed react/server side linting errors
Jul 28, 2025
fd31273
Added seperate file for table and added unit tests
Aug 6, 2025
5cb1a85
Good saving point, file portal search somewhat working, good exmaples…
Aug 13, 2025
3779d92
Another good saving point, going to try to rework search, have submit…
Aug 18, 2025
8776e11
Added functioning file search feature
Sep 8, 2025
2cf8247
Removed tests.py, fixed linting errors
Sep 9, 2025
0459011
Fixed linting errors
Sep 9, 2025
d497c04
last lint check
Sep 9, 2025
915d9af
Merge branch 'main' into task/WP-930
rstijerina Sep 11, 2025
c955edb
Merge branch 'main' into task/WP-930
fnets Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client/modules/_hooks/src/audit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './useGetRecentSession';
export { useGetTapisFileHistory } from './useGetTapisFileHistory';
export { useGetPortalFileHistory } from './useGetPortalFileHistory';
export { useGetUsernames } from './useGetUsernames';
export type { TapisFileAuditEntry } from './useGetTapisFileHistory';
export type { PortalFileAuditEntry } from './useGetPortalFileHistory';
31 changes: 31 additions & 0 deletions client/modules/_hooks/src/audit/useGetPortalFileHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useQuery } from '@tanstack/react-query';

interface PortalFileAuditResponse {
data: PortalFileAuditEntry[];
}

export interface PortalFileAuditEntry {
timestamp: string;
portal: string;
username: string;
action: string;
tracking_id: string;
data: object;
}

async function fetchPortalFileHistory(
filename: string
): Promise<PortalFileAuditResponse> {
const encoded = encodeURIComponent(filename);
const response = await fetch(`/audit/api/file/${encoded}/portal/combined/`);
if (!response.ok) throw new Error(`API Error: ${response.status}`);
return response.json();
}

export function useGetPortalFileHistory(filename: string, enabled: boolean) {
return useQuery<PortalFileAuditResponse, Error>({
queryKey: ['portalFileHistory', filename],
queryFn: () => fetchPortalFileHistory(filename),
enabled,
});
}
35 changes: 35 additions & 0 deletions client/modules/_hooks/src/audit/useGetRecentSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useQuery } from '@tanstack/react-query';

interface PortalAuditResponse {
data: PortalAuditEntry[];
}

export interface PortalAuditEntry {
timestamp: string;
portal: string;
username: string;
action: string;
tracking_id: string;
data: object;
}

async function fetchPortalAudit(
username: string
): Promise<PortalAuditResponse> {
const encoded = encodeURIComponent(username);
const response = await fetch(`/audit/api/user/${encoded}/portal/`);
if (!response.ok) {
throw new Error(
`API request failed: ${response.status} ${response.statusText}`
);
}
return response.json();
}

export function useGetRecentSession(username: string, enabled: boolean) {
return useQuery<PortalAuditResponse, Error>({
queryKey: ['audit', username],
queryFn: () => fetchPortalAudit(username),
enabled,
});
}
31 changes: 31 additions & 0 deletions client/modules/_hooks/src/audit/useGetTapisFileHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useQuery } from '@tanstack/react-query';

interface TapisFileAuditResponse {
data: TapisFileAuditEntry[];
}

export interface TapisFileAuditEntry {
writer_logtime: string; //date&time
obo_user: string; //user
action: string; //action
target_path: string; //path flow - maybe location
source_path: string; //path flow - maybe location
data: object; //details
}

async function fetchTapisFileHistory(
filename: string
): Promise<TapisFileAuditResponse> {
const encoded = encodeURIComponent(filename);
const response = await fetch(`/audit/api/file/${encoded}/tapis/`);
if (!response.ok) throw new Error(`API Error: ${response.status}`);
return response.json();
}

export function useGetTapisFileHistory(filename: string, enabled: boolean) {
return useQuery<TapisFileAuditResponse, Error>({
queryKey: ['fileHistory', filename],
queryFn: () => fetchTapisFileHistory(filename),
enabled,
});
}
15 changes: 15 additions & 0 deletions client/modules/_hooks/src/audit/useGetUsernames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query';

export async function fetchPortalUsernames(): Promise<string[]> {
const response = await fetch('/audit/api/usernames/portal');
if (!response.ok) throw new Error('Failed to fetch usernames');
const data = await response.json();
return data.usernames || [];
}

export function useGetUsernames() {
return useQuery<string[], Error>({
queryKey: ['portalUsernames'],
queryFn: fetchPortalUsernames,
});
}
1 change: 1 addition & 0 deletions client/modules/_hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './datafiles';
export * from './systems';
export * from './notifications';
export * from './onboarding';
export * from './audit';
121 changes: 121 additions & 0 deletions client/src/audit/AuditTrail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState } from 'react';
import { AutoComplete } from 'antd';
import {
useGetRecentSession,
useGetPortalFileHistory,
useGetUsernames,
} from '@client/hooks';
import AuditTrailSessionTable from './AuditTrailSessionTable';
import AuditTrailFileTable from './AuditTrailFileTable';

const AuditTrail: React.FC = () => {
type Mode = 'user-session' | 'portal-file';
const [query, setQuery] = useState('');
const [mode, setMode] = useState<Mode>('user-session');

const { data: allUsernames } = useGetUsernames();
const {
data: portalData,
error: portalError,
isLoading: portalLoading,
refetch: refetchPortal,
} = useGetRecentSession(query, false);

const {
data: fileData,
error: fileError,
isLoading: fileLoading,
refetch: refetchFile,
} = useGetPortalFileHistory(query, false);

const filteredUsernames =
query.length > 0 && allUsernames
? allUsernames
.filter((name) => name.toLowerCase().includes(query.toLowerCase()))
.slice(0, 20)
: [];

const auditData = mode === 'user-session' ? portalData : fileData;
const auditError = mode === 'user-session' ? portalError : fileError;
const auditLoading = mode === 'user-session' ? portalLoading : fileLoading;
const auditRefetch = mode === 'user-session' ? refetchPortal : refetchFile;

const onSearch = (e: React.FormEvent) => {
e.preventDefault();
const trimmed = query.trim();
if (!trimmed) return;
if (trimmed !== query) setQuery(trimmed);
auditRefetch();
};

return (
<div>
<form onSubmit={onSearch} style={{ marginBottom: 16 }}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Antd components would give us some nice UI styling for free

<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
}}
>
<select
value={mode}
onChange={(e) => {
setMode(e.target.value as Mode);
setQuery('');
}}
style={{ marginRight: 8 }}
>
<option value="user-session">Most Recent User Session Data</option>
<option value="portal-file">File search</option>
</select>
{mode === 'user-session' ? (
<AutoComplete
value={query}
style={{ width: '200px' }}
options={filteredUsernames.map((name) => ({
value: name,
label: name,
}))}
onSelect={(value) => setQuery(value)}
onSearch={(searchText) => setQuery(searchText)}
placeholder="Username"
/>
) : (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Filename"
style={{ width: '200px' }}
maxLength={512}
/>
)}
<button
type="submit"
disabled={auditLoading || !query.trim() || query.length > 512}
style={{ marginLeft: '10px' }}
>
{auditLoading ? 'Loading…' : 'Submit'}
</button>
</div>
</form>

{mode === 'user-session' ? (
<AuditTrailSessionTable
auditData={auditData}
auditError={auditError}
auditLoading={auditLoading}
/>
) : (
<AuditTrailFileTable
auditData={auditData}
auditError={auditError}
auditLoading={auditLoading}
searchTerm={(query || '').trim()}
/>
)}
</div>
);
};

export default AuditTrail;
Loading
Loading