1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-05 08:53:14 +00:00

"['dir view'] display file access log in a dialog instead of an independent page (#6673)

- click 'Access Log' in the menu for files in list/grid mode, or
  single-selected file
This commit is contained in:
llj
2024-08-30 07:02:29 +08:00
committed by GitHub
parent 1d785cf824
commit 1495314544
7 changed files with 259 additions and 17 deletions

View File

@@ -0,0 +1,143 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { gettext, siteRoot } from '../../utils/constants';
import { fileAccessLogAPI } from '../../utils/file-access-log-api';
import toaster from '../toast';
import Loading from '../loading';
import EmptyTip from '../empty-tip';
import '../../css/file-access-log.css';
moment.locale(window.app.config.lang);
const propTypes = {
repoID: PropTypes.string.isRequired,
filePath: PropTypes.string.isRequired,
fileName: PropTypes.string.isRequired,
toggleDialog: PropTypes.func.isRequired
};
class FileAccessLog extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true, // first loading
isLoadingMore: false,
items: [],
page: 1,
perPage: 100,
hasNextPage: false
};
}
componentDidMount() {
this.listFileAccessLog(this.state.page);
}
listFileAccessLog = (page) => {
const { repoID, filePath } = this.props;
const { perPage, items } = this.state;
const avatarSize = 24 * 2;
fileAccessLogAPI.listFileAccessLog(repoID, filePath, page, perPage, avatarSize).then((res) => {
const { data: newItems } = res.data;
console.log(newItems);
this.setState({
isLoading: false,
isLoadingMore: false,
page: page,
hasNextPage: newItems.length < perPage ? false : true,
items: items.concat(newItems)
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
this.setState({
isLoading: false,
isLoadingMore: false,
hasNextPage: false
});
});
};
handleScroll = (event) => {
// isLoadingMore: to avoid repeated request
const { page, hasNextPage, isLoadingMore } = this.state;
if (hasNextPage && !isLoadingMore) {
const clientHeight = event.target.clientHeight;
const scrollHeight = event.target.scrollHeight;
const scrollTop = event.target.scrollTop;
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
if (isBottom) { // scroll to the bottom
this.setState({ isLoadingMore: true }, () => {
this.listFileAccessLog(page + 1);
});
}
}
};
render() {
const {
isLoading, hasNextPage, items
} = this.state;
const { fileName } = this.props;
let title = gettext('{placeholder} Access Log');
title = title.replace('{placeholder}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(fileName) + '</span>');
return (
<Modal isOpen={true} toggle={this.props.toggleDialog} className="file-access-log-container">
<ModalHeader toggle={this.props.toggleDialog}>
<span dangerouslySetInnerHTML={{ __html: title }} className="d-flex mw-100"></span>
</ModalHeader>
<ModalBody className="file-access-log-content-container" onScroll={this.handleScroll}>
{isLoading ? <Loading /> : (
<>
{items.length > 0 ? (
<>
<table className="table-hover">
<thead>
<tr>
<th width="25%" className="pl10">{gettext('User')}</th>
<th width="15%">{gettext('Type')}</th>
<th width="40%">{gettext('IP')} / {gettext('Device Name')}</th>
<th width="20%">{gettext('Date')}</th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<tr key={index}>
<td className="pl10">
<img src={item.avatar_url} alt='' width="24" className="rounded-circle mr-2" />
{item.email ? <a href={`${siteRoot}profile/${encodeURIComponent(item.email)}/`} target="_blank" rel="noreferrer">{item.name}</a> : <span>{gettext('Anonymous User')}</span>}
</td>
<td>{item.etype}</td>
<td className="pr-4">
{`${item.ip}${item.device ? '/' + item.device : ''}`}
</td>
<td>{moment(item.time).format('YYYY-MM-DD HH:mm:ss')}</td>
</tr>
);
})}
</tbody>
</table>
{hasNextPage && <Loading />}
</>
) : (
<EmptyTip text={gettext('This file has (apparently) not been accessed yet')} />
)}
</>
)}
</ModalBody>
</Modal>
);
}
}
FileAccessLog.propTypes = propTypes;
export default FileAccessLog;

View File

@@ -21,6 +21,7 @@ import CreateFile from '../dialog/create-file-dialog';
import CreateFolder from '../dialog/create-folder-dialog'; import CreateFolder from '../dialog/create-folder-dialog';
import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog'; import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog';
import toaster from '../toast'; import toaster from '../toast';
import FileAccessLog from '../dialog/file-access-log';
import '../../css/grid-view.css'; import '../../css/grid-view.css';
@@ -72,6 +73,7 @@ class DirentGridView extends React.Component {
imageItems: [], imageItems: [],
imageIndex: 0, imageIndex: 0,
// onmenuClick // onmenuClick
isFileAccessLogDialogOpen: false,
isShareDialogShow: false, isShareDialogShow: false,
isMoveDialogShow: false, isMoveDialogShow: false,
isCopyDialogShow: false, isCopyDialogShow: false,
@@ -377,7 +379,7 @@ class DirentGridView extends React.Component {
this.onCreateFileToggle('.sdoc'); this.onCreateFileToggle('.sdoc');
break; break;
case 'Access Log': case 'Access Log':
this.onAccessLog(currentObject); this.toggleFileAccessLogDialog();
break; break;
case 'Properties': case 'Properties':
this.props.showDirentDetail('info'); this.props.showDirentDetail('info');
@@ -526,10 +528,10 @@ class DirentGridView extends React.Component {
location.href = url; location.href = url;
}; };
onAccessLog = (currentObject) => { toggleFileAccessLogDialog = () => {
let filePath = this.getDirentPath(currentObject); this.setState({
let path = siteRoot + 'repo/file-access/' + this.props.repoID + '/?p=' + encodeURIComponent(filePath) ; isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen
window.open(path); });
}; };
onOpenViaClient = (currentObject) => { onOpenViaClient = (currentObject) => {
@@ -905,6 +907,16 @@ class DirentGridView extends React.Component {
/> />
</ModalPortal> </ModalPortal>
)} )}
{this.state.isFileAccessLogDialogOpen &&
<ModalPortal>
<FileAccessLog
repoID={this.props.repoID}
filePath={direntPath}
fileName={dirent.name}
toggleDialog={this.toggleFileAccessLogDialog}
/>
</ModalPortal>
}
</Fragment> </Fragment>
); );
} }

View File

@@ -18,6 +18,7 @@ import ZipDownloadDialog from '../dialog/zip-download-dialog';
import EditFileTagDialog from '../dialog/edit-filetag-dialog'; import EditFileTagDialog from '../dialog/edit-filetag-dialog';
import EditFileTagPopover from '../popover/edit-filetag-popover'; import EditFileTagPopover from '../popover/edit-filetag-popover';
import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog'; import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog';
import FileAccessLog from '../dialog/file-access-log';
import toaster from '../toast'; import toaster from '../toast';
import FileTag from './file-tag'; import FileTag from './file-tag';
@@ -81,6 +82,7 @@ class DirentListItem extends React.Component {
isOperationShow: false, isOperationShow: false,
highlight: false, highlight: false,
isZipDialogOpen: false, isZipDialogOpen: false,
isFileAccessLogDialogOpen: false,
isMoveDialogShow: false, isMoveDialogShow: false,
isCopyDialogShow: false, isCopyDialogShow: false,
isShareDialogShow: false, isShareDialogShow: false,
@@ -298,7 +300,7 @@ class DirentListItem extends React.Component {
this.onHistory(); this.onHistory();
break; break;
case 'Access Log': case 'Access Log':
this.onAccessLog(); this.toggleFileAccessLogDialog();
break; break;
case 'Properties': case 'Properties':
this.props.onDirentClick(this.props.dirent); this.props.onDirentClick(this.props.dirent);
@@ -415,10 +417,10 @@ class DirentListItem extends React.Component {
location.href = url; location.href = url;
}; };
onAccessLog = () => { toggleFileAccessLogDialog = () => {
let filePath = this.getDirentPath(this.props.dirent); this.setState({
let path = siteRoot + 'repo/file-access/' + this.props.repoID + '/?p=' + encodeURIComponent(filePath) ; isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen
window.open(path); });
}; };
onOpenViaClient = () => { onOpenViaClient = () => {
@@ -935,6 +937,16 @@ class DirentListItem extends React.Component {
/> />
</ModalPortal> </ModalPortal>
} }
{this.state.isFileAccessLogDialogOpen &&
<ModalPortal>
<FileAccessLog
repoID={this.props.repoID}
filePath={direntPath}
fileName={dirent.name}
toggleDialog={this.toggleFileAccessLogDialog}
/>
</ModalPortal>
}
</Fragment> </Fragment>
); );
} }

View File

@@ -14,6 +14,8 @@ import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-di
import ModalPortal from '../modal-portal'; import ModalPortal from '../modal-portal';
import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu';
import toaster from '../toast'; import toaster from '../toast';
import FileAccessLog from '../dialog/file-access-log';
import '../../css/selected-dirents-toolbar.css'; import '../../css/selected-dirents-toolbar.css';
const propTypes = { const propTypes = {
@@ -46,6 +48,7 @@ class MultipleDirOperationToolbar extends React.Component {
super(props); super(props);
this.state = { this.state = {
isZipDialogOpen: false, isZipDialogOpen: false,
isFileAccessLogDialogOpen: false,
isMoveDialogShow: false, isMoveDialogShow: false,
isCopyDialogShow: false, isCopyDialogShow: false,
isMultipleOperation: true, isMultipleOperation: true,
@@ -184,7 +187,7 @@ class MultipleDirOperationToolbar extends React.Component {
this.onHistory(dirent); this.onHistory(dirent);
break; break;
case 'Access Log': case 'Access Log':
this.onAccessLog(dirent); this.toggleFileAccessLogDialog();
break; break;
case 'Properties': case 'Properties':
this.props.showDirentDetail('info'); this.props.showDirentDetail('info');
@@ -247,10 +250,11 @@ class MultipleDirOperationToolbar extends React.Component {
location.href = url; location.href = url;
}; };
onAccessLog = (dirent) => { toggleFileAccessLogDialog = () => {
let filePath = this.getDirentPath(dirent); this.setState({
let path = siteRoot + 'repo/file-access/' + this.props.repoID + '/?p=' + encodeURIComponent(filePath) ; isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen,
window.open(path); showLibContentViewDialogs: !this.state.isFileAccessLogDialogOpen
});
}; };
toggleCancel = () => { toggleCancel = () => {
@@ -450,6 +454,16 @@ class MultipleDirOperationToolbar extends React.Component {
/> />
</ModalPortal> </ModalPortal>
} }
{this.state.isFileAccessLogDialogOpen &&
<ModalPortal>
<FileAccessLog
repoID={this.props.repoID}
filePath={direntPath}
fileName={dirent.name}
toggleDialog={this.toggleFileAccessLogDialog}
/>
</ModalPortal>
}
</Fragment> </Fragment>
)} )}
</Fragment> </Fragment>

View File

@@ -0,0 +1,10 @@
@media(min-width:768px) {
.file-access-log-container {
max-width: 1100px;
}
}
.file-access-log-content-container {
max-height: 500px;
overflow: auto;
}

View File

@@ -0,0 +1,51 @@
import cookie from 'react-cookies';
import { siteRoot } from './constants';
import axios from 'axios';
class FileAccessLogAPI {
init({ server, username, password, token }) {
this.server = server;
this.username = username;
this.password = password;
this.token = token; // none
if (this.token && this.server) {
this.req = axios.create({
baseURL: this.server,
headers: { 'Authorization': 'Token ' + this.token },
});
}
return this;
}
initForSeahubUsage({ siteRoot, xcsrfHeaders }) {
if (siteRoot && siteRoot.charAt(siteRoot.length - 1) === '/') {
let server = siteRoot.substring(0, siteRoot.length - 1);
this.server = server;
} else {
this.server = siteRoot;
}
this.req = axios.create({
headers: {
'X-CSRFToken': xcsrfHeaders,
}
});
return this;
}
listFileAccessLog(repoID, filePath, page, perPage, avatarSize) {
const url = this.server + '/api/v2.1/repos/' + repoID + '/file/access-log/';
const params = {
path: filePath,
page: page || 1,
per_page: perPage || 100,
avatar_size: avatarSize || 64
};
return this.req.get(url, { params: params });
}
}
let fileAccessLogAPI = new FileAccessLogAPI();
let xcsrfHeaders = cookie.load('sfcsrftoken');
fileAccessLogAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
export { fileAccessLogAPI };

View File

@@ -973,9 +973,9 @@ table th {
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8;
text-align: left; text-align: left;
font-weight: normal; font-weight: normal;
font-size: 0.8125rem; font-size: 0.875rem;
line-height: 1.6; line-height: 1.6;
color: #9c9c9c; color: #666;
} }
table td { table td {