diff --git a/VoidCat/Controllers/Admin/AdminController.cs b/VoidCat/Controllers/Admin/AdminController.cs index 61dd484..bc3dd20 100644 --- a/VoidCat/Controllers/Admin/AdminController.cs +++ b/VoidCat/Controllers/Admin/AdminController.cs @@ -13,14 +13,16 @@ public class AdminController : Controller private readonly IFileMetadataStore _fileMetadata; private readonly IFileInfoManager _fileInfo; private readonly IUserStore _userStore; + private readonly IUserUploadsStore _userUploads; public AdminController(IFileStore fileStore, IUserStore userStore, IFileInfoManager fileInfo, - IFileMetadataStore fileMetadata) + IFileMetadataStore fileMetadata, IUserUploadsStore userUploads) { _fileStore = fileStore; _userStore = userStore; _fileInfo = fileInfo; _fileMetadata = fileMetadata; + _userUploads = userUploads; } /// @@ -63,9 +65,23 @@ public class AdminController : Controller /// [HttpPost] [Route("user")] - public async Task> ListUsers([FromBody] PagedRequest request) + public async Task> ListUsers([FromBody] PagedRequest request) { var result = await _userStore.ListUsers(request); - return await result.GetResults(); + + var ret = await result.Results.SelectAwait(async a => + { + var uploads = await _userUploads.ListFiles(a.Id, new(0, int.MaxValue)); + return new AdminListedUser(a, uploads.TotalResults); + }).ToListAsync(); + return new() + { + PageSize = request.PageSize, + Page = request.Page, + TotalResults = result.TotalResults, + Results = ret + }; } + + public record AdminListedUser(PrivateVoidUser User, int Uploads); } \ No newline at end of file diff --git a/VoidCat/spa/src/Admin/Admin.css b/VoidCat/spa/src/Admin/Admin.css index c6c2cbd..ae1361a 100644 --- a/VoidCat/spa/src/Admin/Admin.css +++ b/VoidCat/spa/src/Admin/Admin.css @@ -4,9 +4,31 @@ margin-right: auto; } +.admin h2 { + background-color: #222; + padding: 10px; +} + .admin table { width: 100%; word-break: keep-all; text-overflow: ellipsis; white-space: nowrap; -} \ No newline at end of file + border-collapse: collapse; +} + +.admin table th { + background-color: #222; + text-align: start; +} + +.admin table tr:nth-child(2n) { + background-color: #111; +} + +.admin .btn { + padding: 5px 8px; + border-radius: 3px; + font-size: small; + margin: 2px; +} diff --git a/VoidCat/spa/src/Admin/Admin.js b/VoidCat/spa/src/Admin/Admin.js index b16565c..d448c95 100644 --- a/VoidCat/spa/src/Admin/Admin.js +++ b/VoidCat/spa/src/Admin/Admin.js @@ -4,6 +4,7 @@ import {FileList} from "../FileList"; import {UserList} from "./UserList"; import {Navigate} from "react-router-dom"; import {useApi} from "../Api"; +import {VoidButton} from "../VoidButton"; export function Admin() { const auth = useSelector((state) => state.login.jwt); @@ -11,7 +12,6 @@ export function Admin() { async function deleteFile(e, id) { - e.target.disabled = true; if (window.confirm(`Are you sure you want to delete: ${id}?`)) { let req = await AdminApi.deleteFile(id); if (req.ok) { @@ -20,21 +20,20 @@ export function Admin() { alert("Failed to delete file!"); } } - e.target.disabled = false; } - + if (!auth) { return ; } else { return (
-

Users

+

Users

-

Files

+

Files

{ return - + deleteFile(e, i.id)}>Delete }}/>
diff --git a/VoidCat/spa/src/Admin/UserList.js b/VoidCat/spa/src/Admin/UserList.js index 80523b8..099b5d5 100644 --- a/VoidCat/spa/src/Admin/UserList.js +++ b/VoidCat/spa/src/Admin/UserList.js @@ -5,6 +5,7 @@ import {useApi} from "../Api"; import {logout} from "../LoginState"; import {PageSelector} from "../PageSelector"; import moment from "moment"; +import {VoidButton} from "../VoidButton"; export function UserList() { const {AdminApi} = useApi(); @@ -19,7 +20,7 @@ export function UserList() { page: page, pageSize, sortBy: PagedSortBy.Date, - sortOrder: PageSortOrder.Asc + sortOrder: PageSortOrder.Dsc }; let req = await AdminApi.userList(pageReq); if (req.ok) { @@ -31,17 +32,17 @@ export function UserList() { } } - function renderUser(u) { + function renderUser(obj) { + const user = obj.user; return ( - - {u.id.substring(0, 4)}.. - {moment(u.created).fromNow()} - {moment(u.lastLogin).fromNow()} - 0 - {u.roles.join(", ")} + + {user.displayName} + {moment(user.created).fromNow()} + {moment(user.lastLogin).fromNow()} + {obj.uploads} - - + Delete + SetRoles ); @@ -59,12 +60,11 @@ export function UserList() { - - - - - - + + + + + @@ -73,8 +73,11 @@ export function UserList() { diff --git a/VoidCat/spa/src/FileList.css b/VoidCat/spa/src/FileList.css index 2901fda..2b153b0 100644 --- a/VoidCat/spa/src/FileList.css +++ b/VoidCat/spa/src/FileList.css @@ -3,4 +3,14 @@ table.file-list { word-break: keep-all; text-overflow: ellipsis; white-space: nowrap; + border-collapse: collapse; +} + +table.file-list tr:nth-child(2n) { + background-color: #111; +} + +table.file-list th { + background-color: #222; + text-align: start; } \ No newline at end of file diff --git a/VoidCat/spa/src/FileList.js b/VoidCat/spa/src/FileList.js index 9dd2627..85cfe92 100644 --- a/VoidCat/spa/src/FileList.js +++ b/VoidCat/spa/src/FileList.js @@ -14,7 +14,7 @@ export function FileList(props) { const dispatch = useDispatch(); const [files, setFiles] = useState(); const [page, setPage] = useState(0); - const pageSize = 10; + const pageSize = 20; const [accessDenied, setAccessDenied] = useState(false); async function loadFileList() { @@ -62,16 +62,18 @@ export function FileList(props) {
IdCreatedLast LoginFilesRolesActionsNameCreatedLast LoginFilesActions
- {users ? setPage(x)} page={page} total={users.totalResults} - pageSize={pageSize}/> : null} + {users ? setPage(x)} + page={page} + total={users.totalResults} + pageSize={pageSize}/> : null}
- - - - - - {actions ? : null} + + + + + + {actions ? : null} - {files ? files.results.map(a => renderItem(a)) : } + {files ? files.results.map(a => renderItem(a)) : + + } diff --git a/VoidCat/spa/src/FileUpload.js b/VoidCat/spa/src/FileUpload.js index 71c475f..4dff93e 100644 --- a/VoidCat/spa/src/FileUpload.js +++ b/VoidCat/spa/src/FileUpload.js @@ -86,9 +86,10 @@ export function FileUpload(props) { * @param segment {ArrayBuffer} * @param id {string} * @param editSecret {string?} + * @param fullDigest {string?} Full file hash * @returns {Promise} */ - async function xhrSegment(segment, id, editSecret) { + async function xhrSegment(segment, id, editSecret, fullDigest) { setUState(UploadState.Hashing); const digest = await crypto.subtle.digest(DigestAlgo, segment); setUState(UploadState.Uploading); @@ -116,6 +117,7 @@ export function FileUpload(props) { req.setRequestHeader("V-Content-Type", props.file.type); req.setRequestHeader("V-Filename", props.file.name); req.setRequestHeader("V-Digest", buf2hex(digest)); + req.setRequestHeader("V-Full-Digest", fullDigest); if (auth) { req.setRequestHeader("Authorization", `Bearer ${auth}`); } @@ -134,12 +136,14 @@ export function FileUpload(props) { // upload file in segments of 50MB const UploadSize = 50_000_000; + let digest = await crypto.subtle.digest(DigestAlgo, await props.file.arrayBuffer()); let xhr = null; const segments = props.file.size / UploadSize; for (let s = 0; s < segments; s++) { let offset = s * UploadSize; let slice = props.file.slice(offset, offset + UploadSize, props.file.type); - xhr = await xhrSegment(await slice.arrayBuffer(), xhr?.file?.id, xhr?.file?.metadata?.editSecret); + let segment = await slice.arrayBuffer(); + xhr = await xhrSegment(segment, xhr?.file?.id, xhr?.file?.metadata?.editSecret, buf2hex(digest)); if (!xhr.ok) { break; } diff --git a/VoidCat/spa/src/PageSelector.css b/VoidCat/spa/src/PageSelector.css index a1ec48b..f3a9657 100644 --- a/VoidCat/spa/src/PageSelector.css +++ b/VoidCat/spa/src/PageSelector.css @@ -2,6 +2,7 @@ display: grid; grid-auto-flow: column; width: min-content; + margin-top: 10px; } .page-buttons > div { @@ -11,6 +12,11 @@ cursor: pointer; } +.page-buttons > div.active { + background-color: #333; + font-weight: bold; +} + .page-buttons > div:first-child { border-top-left-radius: 3px; border-bottom-left-radius: 3px; @@ -24,4 +30,5 @@ .page-buttons > small { line-height: 32px; margin-left: 10px; -} \ No newline at end of file +} + diff --git a/VoidCat/spa/src/PageSelector.js b/VoidCat/spa/src/PageSelector.js index 59c57be..dcc8d22 100644 --- a/VoidCat/spa/src/PageSelector.js +++ b/VoidCat/spa/src/PageSelector.js @@ -6,7 +6,7 @@ export function PageSelector(props) { const page = props.page; const onSelectPage = props.onSelectPage; const options = { - showPages: 2, + showPages: 3, ...(props.options || {}) }; @@ -17,7 +17,9 @@ export function PageSelector(props) { let buttons = []; for (let x = first; x <= last; x++) { - buttons.push(
onSelectPage(x)} key={x}>{x+1}
); + buttons.push(
onSelectPage(x)} key={x} className={page === x ? "active" : null}> + {x + 1} +
); } return (
IdNameUploadedSizeEgressActionsIdNameUploadedSizeEgressActions
No files
No files