Skip to content
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
33 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
ee165cf
Added host to middle column dropdown
Nov 5, 2025
1268a9e
New API Response for frontend, stil need modifications to frontend
Nov 17, 2025
2437698
Refactor of UI for updated API return, added utils folder, WORKING
Nov 17, 2025
2c9b4de
Username filter on file search added
Nov 20, 2025
b3ad9bf
Submit Job action added
Nov 26, 2025
11d9812
File Search w/ Tapis Events
Dec 23, 2025
4aa4672
Merge branch 'main' into task/WP-930
erikriv16 Dec 23, 2025
73391b2
Format Checks
Dec 23, 2025
416ca52
Update main.tsx after rebase
Dec 23, 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 }}>
<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