Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/resources/scripts/components/elements/activity/ActivityLogEntry.tsx
10288 views
1
import React from 'react';
2
import { Link } from 'react-router-dom';
3
import Tooltip from '@/components/elements/tooltip/Tooltip';
4
import Translate from '@/components/elements/Translate';
5
import { format, formatDistanceToNowStrict } from 'date-fns';
6
import { ActivityLog } from '@definitions/user';
7
import ActivityLogMetaButton from '@/components/elements/activity/ActivityLogMetaButton';
8
import { FolderOpenIcon, TerminalIcon } from '@heroicons/react/solid';
9
import classNames from 'classnames';
10
import style from './style.module.css';
11
import Avatar from '@/components/Avatar';
12
import useLocationHash from '@/plugins/useLocationHash';
13
import { getObjectKeys, isObject } from '@/lib/objects';
14
15
interface Props {
16
activity: ActivityLog;
17
children?: React.ReactNode;
18
}
19
20
function wrapProperties(value: unknown): any {
21
if (value === null || typeof value === 'string' || typeof value === 'number') {
22
return `<strong>${String(value)}</strong>`;
23
}
24
25
if (isObject(value)) {
26
return getObjectKeys(value).reduce((obj, key) => {
27
if (key === 'count' || (typeof key === 'string' && key.endsWith('_count'))) {
28
return { ...obj, [key]: value[key] };
29
}
30
return { ...obj, [key]: wrapProperties(value[key]) };
31
}, {} as Record<string, unknown>);
32
}
33
34
if (Array.isArray(value)) {
35
return value.map(wrapProperties);
36
}
37
38
return value;
39
}
40
41
export default ({ activity, children }: Props) => {
42
const { pathTo } = useLocationHash();
43
const actor = activity.relationships.actor;
44
const properties = wrapProperties(activity.properties);
45
46
return (
47
<div className={'grid grid-cols-10 py-4 border-b-2 border-gray-800 last:rounded-b last:border-0 group'}>
48
<div className={'hidden sm:flex sm:col-span-1 items-center justify-center select-none'}>
49
<div className={'flex items-center w-10 h-10 rounded-full bg-gray-600 overflow-hidden'}>
50
<Avatar name={actor?.uuid || 'system'} />
51
</div>
52
</div>
53
<div className={'col-span-10 sm:col-span-9 flex'}>
54
<div className={'flex-1 px-4 sm:px-0'}>
55
<div className={'flex items-center text-gray-50'}>
56
<Tooltip placement={'top'} content={actor?.email || 'System User'}>
57
<span>{actor?.username || 'System'}</span>
58
</Tooltip>
59
<span className={'text-gray-400'}>&nbsp;&mdash;&nbsp;</span>
60
<Link
61
to={`#${pathTo({ event: activity.event })}`}
62
className={'transition-colors duration-75 active:text-cyan-400 hover:text-cyan-400'}
63
>
64
{activity.event}
65
</Link>
66
<div className={classNames(style.icons, 'group-hover:text-gray-300')}>
67
{activity.isApi && (
68
<Tooltip placement={'top'} content={'Using API Key'}>
69
<TerminalIcon />
70
</Tooltip>
71
)}
72
{activity.event.startsWith('server:sftp.') && (
73
<Tooltip placement={'top'} content={'Using SFTP'}>
74
<FolderOpenIcon />
75
</Tooltip>
76
)}
77
{children}
78
</div>
79
</div>
80
<p className={style.description}>
81
<Translate ns={'activity'} values={properties} i18nKey={activity.event.replace(':', '.')} />
82
</p>
83
<div className={'mt-1 flex items-center text-sm'}>
84
{activity.ip && (
85
<span>
86
{activity.ip}
87
<span className={'text-gray-400'}>&nbsp;|&nbsp;</span>
88
</span>
89
)}
90
<Tooltip placement={'right'} content={format(activity.timestamp, 'MMM do, yyyy H:mm:ss')}>
91
<span>{formatDistanceToNowStrict(activity.timestamp, { addSuffix: true })}</span>
92
</Tooltip>
93
</div>
94
</div>
95
{activity.hasAdditionalMetadata && <ActivityLogMetaButton meta={activity.properties} />}
96
</div>
97
</div>
98
);
99
};
100
101