mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Creating File Statistics page.
This commit is contained in:
parent
fef8e123a0
commit
e24990aae2
7 changed files with 165 additions and 2 deletions
|
|
@ -661,3 +661,18 @@ export async function getAPIStatus() {
|
|||
let response = await apiGet(`${API_URI}/echo`);
|
||||
return response.status === 200;
|
||||
}
|
||||
|
||||
export async function getFileStatistics() {
|
||||
let response = await apiGet(`${API_URI}/files/statistics`);
|
||||
if (response.status === 200) {
|
||||
const contents = await response.json();
|
||||
return contents['statistics'];
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
title: 'Error!',
|
||||
description: 'Unable to get file statistics',
|
||||
time: 5000,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React, {useContext} from 'react';
|
||||
import {Divider, SegmentGroup} from "semantic-ui-react";
|
||||
import {Divider, SegmentGroup, StatisticLabel, StatisticValue} from "semantic-ui-react";
|
||||
import {Route, Routes} from "react-router-dom";
|
||||
import "../static/wrolpi.css";
|
||||
import {decryptOTP, encryptOTP} from "../api";
|
||||
import {PageContainer, useTitle} from "./Common";
|
||||
import {ThemeContext} from "../contexts/contexts";
|
||||
import {Button, Header, Segment, TextArea} from "./Theme";
|
||||
import {Button, Header, Loader, Segment, Statistic, StatisticGroup, TextArea} from "./Theme";
|
||||
import {useFileStatistics} from "../hooks/customHooks";
|
||||
|
||||
class Encrypt extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -145,10 +146,72 @@ function OTP() {
|
|||
</>
|
||||
}
|
||||
|
||||
function FileStatistics() {
|
||||
useTitle('File Statistics');
|
||||
|
||||
const {s} = useContext(ThemeContext);
|
||||
|
||||
const {statistics} = useFileStatistics();
|
||||
|
||||
let body = <Loader inline active/>;
|
||||
|
||||
if (statistics === undefined) {
|
||||
body = <p {...s}>Failed to fetch statistics</p>;
|
||||
}
|
||||
|
||||
if (statistics !== undefined && statistics !== null) {
|
||||
const {total_count, video_count, pdf_count, ebook_count, archive_count, image_count, zip_count} = statistics;
|
||||
body = <>
|
||||
<StatisticGroup>
|
||||
<Statistic>
|
||||
<StatisticValue>{total_count}</StatisticValue>
|
||||
<StatisticLabel>All Files</StatisticLabel>
|
||||
</Statistic>
|
||||
</StatisticGroup>
|
||||
<StatisticGroup size='small'>
|
||||
<Statistic color='blue'>
|
||||
<StatisticValue>{video_count}</StatisticValue>
|
||||
<StatisticLabel>Videos</StatisticLabel>
|
||||
</Statistic>
|
||||
<Statistic color='red'>
|
||||
<StatisticValue>{pdf_count}</StatisticValue>
|
||||
<StatisticLabel>PDFs</StatisticLabel>
|
||||
</Statistic>
|
||||
<Statistic color='yellow'>
|
||||
<StatisticValue>{ebook_count}</StatisticValue>
|
||||
<StatisticLabel>eBooks</StatisticLabel>
|
||||
</Statistic>
|
||||
<Statistic color='green'>
|
||||
<StatisticValue>{archive_count}</StatisticValue>
|
||||
<StatisticLabel>Archives</StatisticLabel>
|
||||
</Statistic>
|
||||
<Statistic color='pink'>
|
||||
<StatisticValue>{image_count}</StatisticValue>
|
||||
<StatisticLabel>Images</StatisticLabel>
|
||||
</Statistic>
|
||||
</StatisticGroup>
|
||||
<StatisticGroup>
|
||||
<Statistic color='grey'>
|
||||
<StatisticValue>{zip_count}</StatisticValue>
|
||||
<StatisticLabel>ZIP</StatisticLabel>
|
||||
</Statistic>
|
||||
</StatisticGroup>
|
||||
</>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<Header as='h1'>File Statistics</Header>
|
||||
<Segment>
|
||||
{body}
|
||||
</Segment>
|
||||
</>
|
||||
}
|
||||
|
||||
export function AppsRoute(props) {
|
||||
return (<PageContainer>
|
||||
<Routes>
|
||||
<Route path='otp' exact element={<OTP/>}/>
|
||||
<Route path='file_statistics' exact element={<FileStatistics/>}/>
|
||||
</Routes>
|
||||
</PageContainer>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const links = [
|
|||
{
|
||||
key: 'apps', text: 'Apps', links: [
|
||||
{to: '/apps/otp', text: 'One Time Pad', end: true},
|
||||
{to: '/apps/file_statistics', text: 'File Statistics', end: true},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
getDownloaders,
|
||||
getDownloads,
|
||||
getFiles,
|
||||
getFileStatistics,
|
||||
getInventory,
|
||||
getSettings,
|
||||
getStatistics,
|
||||
|
|
@ -718,3 +719,22 @@ export const useInventory = (inventoryId) => {
|
|||
|
||||
return {byCategory, bySubcategory, byName, fetchInventory}
|
||||
}
|
||||
|
||||
export const useFileStatistics = () => {
|
||||
const [statistics, setStatistics] = useState();
|
||||
|
||||
const fetchFileStatistics = async () => {
|
||||
try {
|
||||
const statistics = await getFileStatistics();
|
||||
setStatistics(statistics || undefined);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchFileStatistics();
|
||||
}, []);
|
||||
|
||||
return {statistics};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,3 +135,12 @@ def directories(_, body: schema.DirectoriesRequest):
|
|||
|
||||
body = {'directories': dirs, 'exists': path.exists(), 'is_dir': path.is_dir(), 'is_file': path.is_file()}
|
||||
return response.json(body)
|
||||
|
||||
|
||||
@bp.get('/statistics')
|
||||
@openapi.definition(
|
||||
summary='Get summary statistics of all files',
|
||||
)
|
||||
async def get_statistics(_):
|
||||
statistics = await lib.get_file_statistics()
|
||||
return json_response({'statistics': statistics})
|
||||
|
|
|
|||
|
|
@ -621,3 +621,26 @@ def split_file_name_words(name: str) -> List[str]:
|
|||
except Exception as e:
|
||||
logger.error(f'Failed to split filename into words: {name}', exc_info=e)
|
||||
return [name]
|
||||
|
||||
|
||||
async def get_file_statistics():
|
||||
with get_db_curs() as curs:
|
||||
curs.execute('''
|
||||
SELECT
|
||||
COUNT(path) AS "total_count",
|
||||
COUNT(path) FILTER (WHERE file.mimetype LIKE 'video/%') AS "video_count",
|
||||
COUNT(path) FILTER (WHERE file.mimetype = 'application/pdf') AS "pdf_count",
|
||||
COUNT(path) FILTER (WHERE file.mimetype LIKE 'image/%' AND file.associated = FALSE) AS "image_count",
|
||||
COUNT(path) FILTER (WHERE file.mimetype = 'application/zip') AS "zip_count"
|
||||
FROM
|
||||
file
|
||||
''')
|
||||
statistics = dict(curs.fetchall()[0])
|
||||
|
||||
curs.execute('SELECT COUNT(*) FROM archive')
|
||||
statistics['archive_count'] = curs.fetchall()[0][0]
|
||||
|
||||
curs.execute('SELECT COUNT(*) FROM ebook')
|
||||
statistics['ebook_count'] = curs.fetchall()[0][0]
|
||||
|
||||
return statistics
|
||||
|
|
|
|||
|
|
@ -242,3 +242,35 @@ def test_refresh_files_list(test_session, test_client, make_files_structure):
|
|||
request, response = test_client.post('/api/files/refresh/list', content=content)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
assert test_session.query(File).count() == 1
|
||||
|
||||
|
||||
def test_file_statistics(test_session, test_client, example_pdf, example_mobi, example_epub, video_file):
|
||||
"""A summary of File statistics can be fetched."""
|
||||
# Statistics can be fetched while empty.
|
||||
request, response = test_client.get('/api/files/statistics')
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json == {
|
||||
'statistics': {
|
||||
'archive_count': 0,
|
||||
'ebook_count': 0,
|
||||
'image_count': 0,
|
||||
'pdf_count': 0,
|
||||
'total_count': 0,
|
||||
'video_count': 0,
|
||||
'zip_count': 0},
|
||||
}
|
||||
|
||||
test_client.post('/api/files/refresh')
|
||||
|
||||
request, response = test_client.get('/api/files/statistics')
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json == {
|
||||
'statistics': {
|
||||
'archive_count': 0,
|
||||
'ebook_count': 2,
|
||||
'image_count': 0,
|
||||
'pdf_count': 1,
|
||||
'total_count': 5,
|
||||
'video_count': 1,
|
||||
'zip_count': 0},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue