feat: Added virtual all servers view

This commit is contained in:
florian 2024-05-25 13:58:33 +02:00
parent f49fc74c4d
commit 03134c37d6
4 changed files with 70 additions and 22 deletions

View File

@ -9,13 +9,11 @@ import {
ShieldExclamationIcon, ShieldExclamationIcon,
XMarkIcon, XMarkIcon,
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
import { Server as ServerType } from '../../utils/useUserServers';
import { ServerInfo } from '../../utils/useServerInfo';
import { formatDate, formatFileSize } from '../../utils/utils'; import { formatDate, formatFileSize } from '../../utils/utils';
import { ServerInfo } from '../../utils/useServerInfo';
type ServerProps = { type ServerProps = {
server: ServerType; server: ServerInfo;
serverInfo: ServerInfo;
selectedServer?: string | undefined; selectedServer?: string | undefined;
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>; setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
onTransfer?: (server: string) => void; onTransfer?: (server: string) => void;
@ -28,13 +26,12 @@ const Server = ({
server, server,
selectedServer, selectedServer,
setSelectedServer, setSelectedServer,
serverInfo,
onTransfer, onTransfer,
onCancel, onCancel,
onCheck, onCheck,
blobsOnlyOnThisServer, blobsOnlyOnThisServer,
}: ServerProps) => { }: ServerProps) => {
const readyToUse = !serverInfo.isLoading && !serverInfo.isError; const readyToUse = !server.isLoading && !server.isError;
return ( return (
<div <div
className={ className={
@ -50,24 +47,24 @@ const Server = ({
<div className="flex flex-col grow"> <div className="flex flex-col grow">
<div className="server-name"> <div className="server-name">
{server.name} {server.name}
{serverInfo.isLoading && <span className="ml-2 loading loading-spinner loading-sm"></span>} {server.isLoading && <span className="ml-2 loading loading-spinner loading-sm"></span>}
</div> </div>
{serverInfo.isError ? ( {server.isError ? (
<div className="badge badge-error"> <div className="badge badge-error">
<ExclamationTriangleIcon className="w-4 mr-2" /> Error connecting to server <ExclamationTriangleIcon className="w-4 mr-2" /> Error connecting to server
</div> </div>
) : ( ) : (
<div className="server-stats"> <div className="server-stats">
<div className="server-stat tooltip text-left" data-tip="Number of blobs"> <div className="server-stat tooltip text-left" data-tip="Number of blobs">
<DocumentDuplicateIcon /> {serverInfo.count} <DocumentDuplicateIcon /> {server.count}
</div> </div>
<div className="server-stat tooltip text-left" data-tip="Total size of blobs"> <div className="server-stat tooltip text-left" data-tip="Total size of blobs">
<CubeIcon /> {formatFileSize(serverInfo.size)} <CubeIcon /> {formatFileSize(server.size)}
</div> </div>
<div className="server-stat tooltip text-left" data-tip="Date of last change"> <div className="server-stat tooltip text-left" data-tip="Date of last change">
<ClockIcon /> {formatDate(serverInfo.lastChange)} <ClockIcon /> {formatDate(server.lastChange)}
</div> </div>
{serverInfo.count > 0 && ( {server.count > 0 && !server.virtual && (
<div className="server-stat"> <div className="server-stat">
{blobsOnlyOnThisServer > 0 ? ( {blobsOnlyOnThisServer > 0 ? (
<div> <div>
@ -83,7 +80,7 @@ const Server = ({
</div> </div>
)} )}
</div> </div>
{((selectedServer == server.name && onTransfer) || onCancel) && ( {((selectedServer == server.name && !server.virtual && onTransfer) || onCancel) && (
<div className="server-actions "> <div className="server-actions ">
{selectedServer == server.name && ( {selectedServer == server.name && (
<> <>

View File

@ -1,16 +1,16 @@
import { Cog8ToothIcon } from '@heroicons/react/24/outline'; import { Cog8ToothIcon } from '@heroicons/react/24/outline';
import { useServerInfo } from '../../utils/useServerInfo'; import { ServerInfo, useServerInfo } from '../../utils/useServerInfo';
import { Server as ServerType } from '../../utils/useUserServers'; import { Server as ServerType } from '../../utils/useUserServers';
import Server from './Server'; import Server from './Server';
import './ServerList.css'; import './ServerList.css';
import ServerListPopup from '../ServerListPopup'; import ServerListPopup from '../ServerListPopup';
import { useState } from 'react'; import { useMemo, useState } from 'react';
import { useNDK } from '../../utils/ndk'; import { useNDK } from '../../utils/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
type ServerListProps = { type ServerListProps = {
servers: ServerType[]; servers: ServerInfo[];
selectedServer?: string | undefined; selectedServer?: string | undefined;
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>; setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
onTransfer?: (server: string) => void; onTransfer?: (server: string) => void;
@ -18,6 +18,7 @@ type ServerListProps = {
onCheck?: (server: string) => void; onCheck?: (server: string) => void;
title?: React.ReactElement; title?: React.ReactElement;
manageServers?: boolean; manageServers?: boolean;
withVirtualServers?: boolean;
}; };
export const ServerList = ({ export const ServerList = ({
@ -28,9 +29,10 @@ export const ServerList = ({
onCancel, onCancel,
title, title,
manageServers = false, manageServers = false,
withVirtualServers = false,
}: ServerListProps) => { }: ServerListProps) => {
const { ndk, user } = useNDK(); const { ndk, user } = useNDK();
const { serverInfo, distribution } = useServerInfo(); const { distribution } = useServerInfo();
const blobsWithOnlyOneOccurance = Object.values(distribution) const blobsWithOnlyOneOccurance = Object.values(distribution)
.filter(d => d.servers.length == 1) .filter(d => d.servers.length == 1)
.map(d => ({ ...d.blob, server: d.servers[0] })); .map(d => ({ ...d.blob, server: d.servers[0] }));
@ -58,6 +60,8 @@ export const ServerList = ({
await ev.publish(); await ev.publish();
}; };
const serversToList = useMemo(() => withVirtualServers ? servers : servers.filter(s => !s.virtual), [servers, withVirtualServers])
return ( return (
<> <>
<div className={`flex flex-row py-4 ${!title ? 'justify-end' : ''}`}> <div className={`flex flex-row py-4 ${!title ? 'justify-end' : ''}`}>
@ -76,14 +80,13 @@ export const ServerList = ({
isOpen={isDialogOpen} isOpen={isDialogOpen}
onClose={handleCloseDialog} onClose={handleCloseDialog}
onSave={handleSaveServers} onSave={handleSaveServers}
initialServers={Object.values(serverInfo)} initialServers={servers.filter(s => !s.virtual)}
/> />
<div className="server-list"> <div className="server-list">
{servers.map(server => ( {serversToList.map(server => (
<Server <Server
key={server.name} key={server.name}
serverInfo={serverInfo[server.name]}
server={server} server={server}
selectedServer={selectedServer} selectedServer={selectedServer}
setSelectedServer={setSelectedServer} setSelectedServer={setSelectedServer}

View File

@ -4,7 +4,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk'; import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
import { useNDK } from '../utils/ndk'; import { useNDK } from '../utils/ndk';
import BlobList from '../components/BlobList/BlobList'; import BlobList from '../components/BlobList/BlobList';
import { useServerInfo } from '../utils/useServerInfo'; import { ServerInfo, useServerInfo } from '../utils/useServerInfo';
import { ServerList } from '../components/ServerList/ServerList'; import { ServerList } from '../components/ServerList/ServerList';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -62,6 +62,7 @@ function Home() {
onCheck={() => navigate('/check/' + selectedServer)} onCheck={() => navigate('/check/' + selectedServer)}
title={<>Servers</>} title={<>Servers</>}
manageServers={true} manageServers={true}
withVirtualServers={true}
></ServerList> ></ServerList>
{selectedServer && serverInfo[selectedServer] && selectedServerBlobs && ( {selectedServer && serverInfo[selectedServer] && selectedServerBlobs && (

View File

@ -7,6 +7,7 @@ import { useUserServers } from './useUserServers';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export type ServerInfo = { export type ServerInfo = {
virtual: boolean;
count: number; count: number;
size: number; size: number;
lastChange: number; lastChange: number;
@ -21,6 +22,21 @@ type BlobDictionary = {
[key: string]: { blob: BlobDescriptor; servers: string[] }; [key: string]: { blob: BlobDescriptor; servers: string[] };
}; };
const mergeBlobs = (
baseBlobs: BlobDescriptor[],
newBlobs: BlobDescriptor[],
existingBlobs: { [key: string]: boolean }
): BlobDescriptor[] => {
const result = [...baseBlobs];
for (const blob of newBlobs) {
if (!existingBlobs[blob.sha256]) {
existingBlobs[blob.sha256] = true;
result.push(blob);
}
}
return result;
};
export const useServerInfo = () => { export const useServerInfo = () => {
const servers = useUserServers(); const servers = useUserServers();
const { user, signEventTemplate } = useNDK(); const { user, signEventTemplate } = useNDK();
@ -48,6 +64,7 @@ export const useServerInfo = () => {
servers.forEach((server, sx) => { servers.forEach((server, sx) => {
info[server.name] = { info[server.name] = {
...server, ...server,
virtual: false,
blobs: blobs[sx].data, blobs: blobs[sx].data,
isLoading: blobs[sx].isLoading, isLoading: blobs[sx].isLoading,
isError: blobs[sx].isError, isError: blobs[sx].isError,
@ -59,6 +76,36 @@ export const useServerInfo = () => {
return info; return info;
}, [servers, blobs]); }, [servers, blobs]);
const allServersAggregation = useMemo(() => {
const serversInfos = Object.values(serverInfo);
const existingBlobs: { [key: string]: boolean } = {};
const initial: ServerInfo = {
virtual: true,
count: 0,
size: 0,
lastChange: 0,
isLoading: false,
isError: false,
name: 'All servers',
url: 'all',
blobs: [],
};
const allInfo = serversInfos.reduce(
(acc, server) => ({
...acc,
lastChange: Math.max(acc.lastChange, server.lastChange),
isLoading: acc.isLoading || server.isLoading,
isError: acc.isError || server.isError,
blobs: mergeBlobs(acc.blobs || [], server.blobs || [], existingBlobs),
}),
initial
);
allInfo.size = allInfo.blobs?.reduce((acc, blob) => acc + blob.size, 0) || 0;
allInfo.count = allInfo.blobs?.length || 0;
return { [allInfo.name]: allInfo, ...serverInfo };
}, [serverInfo]);
const distribution = useMemo(() => { const distribution = useMemo(() => {
const dict: BlobDictionary = {}; const dict: BlobDictionary = {};
@ -80,5 +127,5 @@ export const useServerInfo = () => {
return dict; return dict;
}, [servers, serverInfo]); }, [servers, serverInfo]);
return { serverInfo, distribution }; return { serverInfo: allServersAggregation, distribution };
}; };