feat: Added distribution info

This commit is contained in:
florian 2024-03-27 14:02:41 +01:00
parent e66b41560d
commit a88f48751f
8 changed files with 146 additions and 87 deletions

View File

@ -3,7 +3,7 @@
}
.content {
@apply flex flex-col self-center sm:w-4/6 w-full;
@apply flex flex-col self-center sm:w-10/12 lg:w-4/6 w-full;
}
.title {

View File

@ -0,0 +1,94 @@
import {
ArrowPathIcon,
ArrowUpOnSquareStackIcon,
CheckBadgeIcon,
ClockIcon,
CubeIcon,
DocumentDuplicateIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
ServerIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import { Server } from '../../utils/useServers';
import { ServerInfo } from '../../utils/useServerInfo';
import { formatDate, formatFileSize } from '../../utils';
type ServerProps = {
server: Server;
serverInfo: ServerInfo;
selectedServer?: string | undefined;
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
onTransfer?: (server: string) => void;
onCancel?: () => void;
blobsOnlyOnThisServer: number;
};
const Server = ({
server,
selectedServer,
setSelectedServer,
serverInfo,
onTransfer,
onCancel,
blobsOnlyOnThisServer,
}: ServerProps) => {
return (
<div
className={
`server ${selectedServer == server.name ? 'selected' : ''} ` +
`${setSelectedServer ? ' hover:bg-neutral-700 cursor-pointer' : ''} `
}
key={server.name}
onClick={() => setSelectedServer && setSelectedServer(server.name)}
>
<div className="server-icon">
<ServerIcon />
</div>
<div className="flex flex-col grow">
<div className="server-name">
{server.name}
{serverInfo.isLoading && <ArrowPathIcon className="loading" />}
</div>
<div className="server-stats">
<div className="server-stat">
<DocumentDuplicateIcon /> {serverInfo.count}
</div>
<div className="server-stat">
<CubeIcon /> {formatFileSize(serverInfo.size)}
</div>
<div className="server-stat">
<ClockIcon /> {formatDate(serverInfo.lastChange)}
</div>
<div className="server-stat">
{blobsOnlyOnThisServer > 0 ? (
<div>
<ExclamationTriangleIcon /> {blobsOnlyOnThisServer} objects only available here
</div>
) : (
<div>
<CheckBadgeIcon /> all objects distributed.
</div>
)}
</div>
</div>
</div>
{((selectedServer == server.name && onTransfer) || onCancel) && (
<div className="server-actions">
{selectedServer == server.name && onTransfer && (
<a onClick={() => onTransfer(server.name)}>
<ArrowUpOnSquareStackIcon /> Transfer
</a>
)}
{onCancel && (
<a onClick={() => onCancel()}>
<XMarkIcon />
</a>
)}
</div>
)}
</div>
);
};
export default Server;

View File

@ -15,7 +15,8 @@
}
.server-stats {
@apply flex flex-row gap-8;
@apply grid grid-cols-4 gap-8;
grid-template-columns: 4em 6em 8em auto;
}
.server-stat svg {

View File

@ -1,19 +1,10 @@
import {
ArrowPathIcon,
ArrowUpOnSquareStackIcon,
ClockIcon,
CubeIcon,
DocumentDuplicateIcon,
ServerIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import { formatDate, formatFileSize } from '../../utils';
import { useServerInfo } from '../../utils/useServerInfo';
import { Server as ServerType } from '../../utils/useServers';
import Server from './Server';
import './ServerList.css';
import { Server } from '../../utils/useServers';
type ServerListProps = {
servers: Server[];
servers: ServerType[];
selectedServer?: string | undefined;
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
onTransfer?: (server: string) => void;
@ -21,54 +12,24 @@ type ServerListProps = {
};
export const ServerList = ({ servers, selectedServer, setSelectedServer, onTransfer, onCancel }: ServerListProps) => {
const serverInfo = useServerInfo();
//
const { serverInfo, distribution } = useServerInfo();
const blobsWithOnlyOneOccurance = Object.values(distribution)
.filter(d => d.servers.length == 1)
.map(d => ({ ...d.blob, server: d.servers[0] }));
return (
<div className="server-list">
{servers.map((server, sx) => (
<div
className={
`server ${selectedServer == server.name ? 'selected' : ''} ` +
`${setSelectedServer ? ' hover:bg-neutral-700 cursor-pointer' : ''} `
}
key={sx}
onClick={() => setSelectedServer && setSelectedServer(server.name)}
>
<div className="server-icon">
<ServerIcon />
</div>
<div className="flex flex-col grow">
<div className="server-name">
{server.name}
{serverInfo[server.name].isLoading && <ArrowPathIcon className="loading" />}
</div>
<div className="server-stats">
<div className="server-stat">
<DocumentDuplicateIcon /> {serverInfo[server.name].count}
</div>
<div className="server-stat">
<CubeIcon /> {formatFileSize(serverInfo[server.name].size)}
</div>
<div className="server-stat">
<ClockIcon /> {formatDate(serverInfo[server.name].lastChange)}
</div>
</div>
</div>
{((selectedServer == server.name && onTransfer) || onCancel) && (
<div className="server-actions">
{selectedServer == server.name && onTransfer && (
<a onClick={() => onTransfer(server.name)}>
<ArrowUpOnSquareStackIcon /> Transfer
</a>
)}
{onCancel && (
<a onClick={() => onCancel()}>
<XMarkIcon />
</a>
)}
</div>
)}
</div>
{servers.map(server => (
<Server
key={server.name}
serverInfo={serverInfo[server.name]}
server={server}
selectedServer={selectedServer}
setSelectedServer={setSelectedServer}
onTransfer={onTransfer}
onCancel={onCancel}
blobsOnlyOnThisServer={blobsWithOnlyOneOccurance.filter(b => b.server == server.name).length}
></Server>
))}
</div>
);

View File

@ -24,35 +24,13 @@ import { useNavigate } from 'react-router-dom';
function Home() {
const { loginWithExtension, signEventTemplate } = useNDK();
const [selectedServer, setSelectedServer] = useState<string | undefined>();
const serverInfo = useServerInfo();
const { serverInfo } = useServerInfo();
const navigate = useNavigate();
useEffect(() => {
loginWithExtension();
}, []);
/*,
combine: (results) => {
const dict: BlobDictionary = {};
results.forEach((server) =>
server.data && server.data.forEach((blob: BlobDescriptor) => {
if (dict[blob.sha256]) {
dict[blob.sha256].urls.push(blob.url);
} else {
dict[blob.sha256] = {
...blob,
urls: [blob.url],
};
}
})
);
return {
data: dict,
};
},*/
const queryClient = useQueryClient();
const deleteBlob = useMutation({
@ -64,7 +42,7 @@ function Home() {
queryClient.setQueryData(['blobs', variables.serverName], (oldData: BlobDescriptor[]) =>
oldData ? oldData.filter(b => b.sha256 !== variables.hash) : oldData
);
console.log({ key: ['blobs', variables.serverName] });
// console.log({ key: ['blobs', variables.serverName] });
},
});

View File

@ -28,7 +28,7 @@ type TransferStatus = {
export const Transfer = () => {
const { source: transferSource } = useParams();
const navigate = useNavigate();
const serverInfo = useServerInfo();
const {serverInfo} = useServerInfo();
const [transferTarget, setTransferTarget] = useState<string | undefined>();
const { signEventTemplate } = useNDK();
const queryClient = useQueryClient();

View File

@ -15,6 +15,10 @@ export type ServerInfo = {
blobs?: BlobDescriptor[];
};
type BlobDictionary = {
[key: string]: { blob: BlobDescriptor; servers: string[] };
};
export const useServerInfo = () => {
const servers = useServers();
const { user, signEventTemplate } = useNDK();
@ -58,5 +62,26 @@ export const useServerInfo = () => {
return info;
}, [servers, blobs]);
return serverInfo;
const distribution = useMemo(() => {
const dict: BlobDictionary = {};
servers.forEach(server => {
const si = serverInfo[server.name];
si.blobs &&
si.blobs.forEach((blob: BlobDescriptor) => {
if (dict[blob.sha256]) {
dict[blob.sha256].servers.push(server.name);
} else {
dict[blob.sha256] = {
blob,
servers: [server.name],
};
}
});
});
return dict;
}, [servers, serverInfo]);
return {serverInfo, distribution};
};

View File

@ -22,7 +22,7 @@ export const useServers = (): Server[] => {
const pubkey = user?.npub && (nip19.decode(user?.npub).data as string); // TODO validate type
const serverListEvent = useEvent({ kinds: [10063 as NDKKind], authors: [pubkey!] }, { disable: !pubkey });
console.log(serverListEvent);
const servers = useMemo(() => {
const serverUrls = uniqAndSort(
[...(serverListEvent?.getMatchingTags('r').map(t => t[1]) || []), ...additionalServers].map(s =>