Skip to content

Commit 0b4936f

Browse files
committed
Break out rows for activity; show metadata icon
1 parent 33823b6 commit 0b4936f

File tree

4 files changed

+125
-67
lines changed

4 files changed

+125
-67
lines changed

app/Http/Controllers/Api/Client/ActivityLogController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __invoke(ClientApiRequest $request): array
2121
AllowedFilter::partial('event'),
2222
])
2323
->allowedSorts(['timestamp'])
24-
->paginate(min($request->query('per_page', 50), 100))
24+
->paginate(min($request->query('per_page', 25), 100))
2525
->appends($request->query());
2626

2727
return $this->fractal->collection($activity)

resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx

Lines changed: 9 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ import { ActivityLogFilters, useActivityLogs } from '@/api/account/activity';
33
import { useFlashKey } from '@/plugins/useFlash';
44
import PageContentBlock from '@/components/elements/PageContentBlock';
55
import FlashMessageRender from '@/components/FlashMessageRender';
6-
import { format, formatDistanceToNowStrict } from 'date-fns';
76
import { Link } from 'react-router-dom';
87
import PaginationFooter from '@/components/elements/table/PaginationFooter';
9-
import { UserIcon } from '@heroicons/react/outline';
10-
import Tooltip from '@/components/elements/tooltip/Tooltip';
118
import { DesktopComputerIcon, XCircleIcon } from '@heroicons/react/solid';
129
import { useLocation } from 'react-router';
1310
import Spinner from '@/components/elements/Spinner';
1411
import { styles as btnStyles } from '@/components/elements/button/index';
1512
import classNames from 'classnames';
16-
import Translate from '@/components/elements/Translate';
13+
import ActivityLogEntry from '@/components/elements/activity/ActivityLogEntry';
14+
import Tooltip from '@/components/elements/tooltip/Tooltip';
1715

1816
export default () => {
1917
const location = useLocation();
@@ -35,15 +33,6 @@ export default () => {
3533
clearAndAddHttpError(error);
3634
}, [ error ]);
3735

38-
const queryTo = (params: Record<string, string>): string => {
39-
const current = new URLSearchParams(location.search);
40-
Object.keys(params).forEach(key => {
41-
current.set(key, params[key]);
42-
});
43-
44-
return current.toString();
45-
};
46-
4736
return (
4837
<PageContentBlock title={'Account Activity Log'}>
4938
<FlashMessageRender byKey={'account'}/>
@@ -63,59 +52,13 @@ export default () => {
6352
:
6453
<div className={'bg-gray-700'}>
6554
{data?.items.map((activity) => (
66-
<div
67-
key={`${activity.event}|${activity.timestamp.toString()}`}
68-
className={'grid grid-cols-10 py-4 border-b-2 border-gray-800 last:rounded-b last:border-0'}
69-
>
70-
<div className={'col-span-2 sm:col-span-1 flex items-center justify-center select-none'}>
71-
<div className={'flex items-center w-8 h-8 rounded-full bg-gray-600 overflow-hidden'}>
72-
{activity.relationships.actor ?
73-
<img src={activity.relationships.actor.image} alt={'User avatar'}/>
74-
:
75-
<UserIcon className={'w-5 h-5 mx-auto'}/>
76-
}
77-
</div>
78-
</div>
79-
<div className={'col-span-8 sm:col-span-9'}>
80-
<div className={'flex items-center text-gray-50'}>
81-
{activity.relationships.actor?.username || 'system'}
82-
<span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span>
83-
<Link
84-
to={`?${queryTo({ event: activity.event })}`}
85-
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
86-
>
87-
{activity.event}
88-
</Link>
89-
{typeof activity.properties.useragent === 'string' &&
90-
<Tooltip content={activity.properties.useragent} placement={'top'}>
91-
<DesktopComputerIcon className={'ml-2 w-4 h-4 cursor-pointer'}/>
92-
</Tooltip>
93-
}
94-
</div>
95-
<p className={'mt-1 text-sm break-words line-clamp-2 pr-4'}>
96-
<Translate ns={'activity'} values={activity.properties}>
97-
{activity.event.replace(':', '.')}
98-
</Translate>
99-
</p>
100-
<div className={'mt-1 flex items-center text-sm'}>
101-
<Link
102-
to={`?${queryTo({ ip: activity.ip })}`}
103-
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
104-
>
105-
{activity.ip}
106-
</Link>
107-
<span className={'text-gray-400'}>&nbsp;|&nbsp;</span>
108-
<Tooltip
109-
placement={'right'}
110-
content={format(activity.timestamp, 'MMM do, yyyy h:mma')}
111-
>
112-
<span>
113-
{formatDistanceToNowStrict(activity.timestamp, { addSuffix: true })}
114-
</span>
115-
</Tooltip>
116-
</div>
117-
</div>
118-
</div>
55+
<ActivityLogEntry key={activity.timestamp.toString() + activity.event} activity={activity}>
56+
{typeof activity.properties.useragent === 'string' &&
57+
<Tooltip content={activity.properties.useragent} placement={'top'}>
58+
<DesktopComputerIcon className={'ml-2 w-4 h-4 cursor-pointer'}/>
59+
</Tooltip>
60+
}
61+
</ActivityLogEntry>
11962
))}
12063
</div>
12164
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import { UserIcon } from '@heroicons/react/outline';
3+
import { Link } from 'react-router-dom';
4+
import Tooltip from '@/components/elements/tooltip/Tooltip';
5+
import Translate from '@/components/elements/Translate';
6+
import { format, formatDistanceToNowStrict } from 'date-fns';
7+
import { ActivityLog } from '@definitions/user';
8+
import { useLocation } from 'react-router';
9+
import ActivityLogMetaButton from '@/components/elements/activity/ActivityLogMetaButton';
10+
11+
interface Props {
12+
activity: ActivityLog;
13+
children?: React.ReactNode;
14+
}
15+
16+
export default ({ activity, children }: Props) => {
17+
const location = useLocation();
18+
const actor = activity.relationships.actor;
19+
20+
const queryTo = (params: Record<string, string>): string => {
21+
const current = new URLSearchParams(location.search);
22+
Object.keys(params).forEach(key => current.set(key, params[key]));
23+
24+
return current.toString();
25+
};
26+
27+
return (
28+
<div className={'grid grid-cols-10 py-4 border-b-2 border-gray-800 last:rounded-b last:border-0 group'}>
29+
<div className={'col-span-2 sm:col-span-1 flex items-center justify-center select-none'}>
30+
<div className={'flex items-center w-8 h-8 rounded-full bg-gray-600 overflow-hidden'}>
31+
{actor ?
32+
<img src={actor.image} alt={'User avatar'}/>
33+
:
34+
<UserIcon className={'w-5 h-5 mx-auto'}/>
35+
}
36+
</div>
37+
</div>
38+
<div className={'col-span-8 sm:col-span-9 flex'}>
39+
<div className={'flex-1'}>
40+
<div className={'flex items-center text-gray-50'}>
41+
<Tooltip placement={'top'} content={actor?.email || 'System User'}>
42+
<span>{actor?.username || 'System'}</span>
43+
</Tooltip>
44+
<span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span>
45+
<Link
46+
to={`?${queryTo({ event: activity.event })}`}
47+
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
48+
>
49+
{activity.event}
50+
</Link>
51+
{children}
52+
</div>
53+
<p className={'mt-1 text-sm break-words line-clamp-2 pr-4'}>
54+
<Translate ns={'activity'} values={activity.properties}>
55+
{activity.event.replace(':', '.')}
56+
</Translate>
57+
</p>
58+
<div className={'mt-1 flex items-center text-sm'}>
59+
<Link
60+
to={`?${queryTo({ ip: activity.ip })}`}
61+
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
62+
>
63+
{activity.ip}
64+
</Link>
65+
<span className={'text-gray-400'}>&nbsp;|&nbsp;</span>
66+
<Tooltip
67+
placement={'right'}
68+
content={format(activity.timestamp, 'MMM do, yyyy h:mma')}
69+
>
70+
<span>
71+
{formatDistanceToNowStrict(activity.timestamp, { addSuffix: true })}
72+
</span>
73+
</Tooltip>
74+
</div>
75+
</div>
76+
<ActivityLogMetaButton meta={activity.properties}/>
77+
</div>
78+
</div>
79+
);
80+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { useState } from 'react';
2+
import { isEmptyObject } from '@/helpers';
3+
import { ClipboardListIcon } from '@heroicons/react/outline';
4+
import { Dialog } from '@/components/elements/dialog';
5+
import { Button } from '@/components/elements/button/index';
6+
7+
export default ({ meta }: { meta: Record<string, unknown> }) => {
8+
const [ open, setOpen ] = useState(false);
9+
10+
if (isEmptyObject(meta)) {
11+
return null;
12+
}
13+
14+
return (
15+
<div className={'self-center mx-4'}>
16+
<Dialog
17+
open={open}
18+
onClose={() => setOpen(false)}
19+
hideCloseIcon
20+
title={'Metadata'}
21+
>
22+
<pre>{JSON.stringify(meta, null, 2)}</pre>
23+
<Dialog.Buttons>
24+
<Button.Text onClick={() => setOpen(false)}>Close</Button.Text>
25+
</Dialog.Buttons>
26+
</Dialog>
27+
<button
28+
className={'p-2 transition-colors duration-100 text-gray-400 group-hover:text-gray-300 group-hover:hover:text-gray-50'}
29+
onClick={() => setOpen(true)}
30+
>
31+
<ClipboardListIcon className={'w-5 h-5'}/>
32+
</button>
33+
</div>
34+
);
35+
};

0 commit comments

Comments
 (0)