Adding links between domain page and archives page.

This commit is contained in:
Roland 2025-12-17 17:27:17 -07:00
parent a8ff1a1c38
commit 8a01ddc040
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 123 additions and 18 deletions

View file

@ -10,9 +10,14 @@ import {
GridRow,
Image,
Input,
PlaceholderHeader,
PlaceholderLine,
StatisticLabel,
StatisticValue,
TableCell,
TextArea,
} from "semantic-ui-react";
import Icon from "semantic-ui-react/dist/commonjs/elements/Icon";
import {
APIButton,
BackButton,
@ -23,6 +28,7 @@ import {
FileIcon,
findPosterPath,
humanFileSize,
humanNumber,
InfoHeader,
isoDatetimeToAgoPopup,
mimetypeColor,
@ -48,12 +54,12 @@ import {
import {CollectionTagModal} from "./collections/CollectionTagModal";
import {Link, Route, Routes, useLocation, useNavigate, useParams} from "react-router";
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
import {useArchive, useDomain, useDomains, useOneQuery, useSearchArchives, useSearchOrder} from "../hooks/customHooks";
import {useArchive, useDomain, useDomains, useOneQuery, useSearchArchives, useSearchDomain, useSearchOrder} from "../hooks/customHooks";
import {FileCards, FileRowTagIcon, FilesView} from "./Files";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import _ from "lodash";
import {Media, ThemeContext} from "../contexts/contexts";
import {Button, Card, darkTheme, Header, Loader, Popup, Segment, Tab} from "./Theme";
import {Button, Card, darkTheme, Header, Loader, Placeholder, Popup, Segment, Statistic, Tab} from "./Theme";
import {taggedImageLabel, TagsSelector} from "../Tags";
import {toast} from "react-semantic-toasts-2";
import {API_ARCHIVE_UPLOAD_URI, Downloaders} from "./Vars";
@ -384,6 +390,32 @@ const DOMAIN_ROUTES = {
searchParam: 'domain'
};
function DomainStatistics({statistics}) {
if (!statistics) {
return <></>
}
return <Segment>
<Header as='h1'>Statistics</Header>
<Statistic>
<StatisticValue>{statistics.archive_count}</StatisticValue>
<StatisticLabel>Archives</StatisticLabel>
</Statistic>
<Statistic>
<StatisticValue>{humanFileSize(statistics.size, true)}</StatisticValue>
<StatisticLabel>Total Size</StatisticLabel>
</Statistic>
<Statistic>
<StatisticValue>{humanFileSize(statistics.largest_archive, true)}</StatisticValue>
<StatisticLabel>Largest Archive</StatisticLabel>
</Statistic>
<Statistic>
<StatisticValue>{humanNumber(statistics.archive_tags)}</StatisticValue>
<StatisticLabel>Archive Tags</StatisticLabel>
</Statistic>
</Segment>
}
export function DomainsPage() {
useTitle('Archive Domains');
@ -565,6 +597,10 @@ export function DomainEditPage() {
return <>
<BackButton/>
<Link to={`/archive?domain=${domain?.domain}`}>
<Button>Archives</Button>
</Link>
<CollectionEditForm
form={form}
title={`Edit Domain: ${domain?.domain || '...'}`}
@ -617,6 +653,8 @@ export function DomainEditPage() {
fetchDownloads={fetchDomain}
/>
</Segment>
{domain && domain.statistics && <DomainStatistics statistics={domain.statistics}/>}
</>;
}
@ -666,7 +704,16 @@ function ArchiveSettingsPage() {
function ArchivesPage() {
const [selectedArchives, setSelectedArchives] = useState([]);
useTitle('Archives');
const {domain, domains} = useSearchDomain();
// Find the domain object from the domains list to get the ID for the edit link
const domainObj = domain && domains ? domains.find(d => d.domain === domain) : null;
let title = 'Archives';
if (domainObj && domainObj.domain) {
title = `${domainObj.domain} Archives`;
}
useTitle(title);
const {
archives,
@ -763,7 +810,29 @@ function ArchivesPage() {
placeholder='Search Archives...'
/>;
// Domain header with edit link when filtering by domain
let header;
if (domainObj && domainObj.domain) {
const editLink = `/archive/domain/${domainObj.id}/edit`;
header = <>
<Header as='h1'>
{domainObj.domain}
<Link to={editLink}>
<Icon name='edit' style={{marginLeft: '0.5em'}}/>
</Link>
</Header>
</>;
} else if (domain) {
// Domain filter is set but domain object not loaded yet
header = <Placeholder style={{marginBottom: '1em'}}>
<PlaceholderHeader>
<PlaceholderLine/>
</PlaceholderHeader>
</Placeholder>;
}
return <>
{header}
<Media at='mobile'>
<Grid>
<Grid.Row>

View file

@ -25,6 +25,7 @@ logger = logger.getChild(__name__)
__all__ = [
'get_collections',
'get_collection_with_stats',
'get_domain_statistics',
'update_collection',
'refresh_collection',
'tag_collection',
@ -162,21 +163,11 @@ def get_collection_with_stats(session: Session, collection_id: int) -> dict:
# This can be extended for different collection types
if collection.kind == 'domain':
# Add archive statistics for domain collections
# Local imports to avoid circular import: collections -> archive -> collections
from modules.archive import Archive
from wrolpi.files.models import FileGroup
stats_query = session.query(
func.count(Archive.id).label('archive_count'),
func.sum(FileGroup.size).label('size')
).outerjoin(
FileGroup, FileGroup.id == Archive.file_group_id
).filter(
Archive.collection_id == collection_id
).one()
data['archive_count'] = stats_query.archive_count or 0
data['size'] = stats_query.size or 0
# Use the detailed statistics function (similar to Channel.get_statistics())
statistics = get_domain_statistics(session, collection_id)
data['archive_count'] = statistics['archive_count']
data['size'] = statistics['size']
data['statistics'] = statistics
data['domain'] = data['name'] # Alias for backward compatibility
elif collection.kind == 'channel':
@ -618,3 +609,48 @@ def search_collections(
collections = query.all()
return [collection.__json__() for collection in collections]
def get_domain_statistics(session: Session, collection_id: int) -> Dict:
"""
Get detailed statistics for a domain collection.
Similar to Channel.get_statistics(), this returns comprehensive stats about
a domain collection's archives.
Args:
session: Database session
collection_id: The domain collection ID
Returns:
Dict with:
- archive_count: Number of archives in the domain
- size: Total size of all archive files
- largest_archive: Size of the largest archive
- archive_tags: Count of archives that have at least one tag
"""
# Local imports to avoid circular import: collections -> archive -> collections
from modules.archive.models import Archive
from wrolpi.files.models import FileGroup
from wrolpi.tags import TagFile
# Use a single query with aggregate functions similar to Channel.get_statistics()
stats_query = session.query(
func.count(Archive.id).label('archive_count'),
func.coalesce(func.sum(FileGroup.size), 0).label('size'),
func.coalesce(func.max(FileGroup.size), 0).label('largest_archive'),
func.count(Archive.id).filter(TagFile.file_group_id.isnot(None)).label('archive_tags')
).outerjoin(
FileGroup, FileGroup.id == Archive.file_group_id
).outerjoin(
TagFile, FileGroup.id == TagFile.file_group_id
).filter(
Archive.collection_id == collection_id
).one()
return {
'archive_count': stats_query.archive_count or 0,
'size': int(stats_query.size or 0),
'largest_archive': int(stats_query.largest_archive or 0),
'archive_tags': stats_query.archive_tags or 0,
}