mirror of
https://git.v0l.io/florian/bouquet.git
synced 2025-03-18 01:13:00 +01:00
feat: Added router
This commit is contained in:
parent
3a8ba07903
commit
e66b41560d
@ -23,7 +23,8 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"nostr-tools": "^2.3.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.22.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-plugin-query": "^5.28.6",
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useNDK } from '../../ndk';
|
||||
import './Layout.css';
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactElement }) => {
|
||||
export const Layout = () => {
|
||||
const { user } = useNDK();
|
||||
|
||||
return (
|
||||
@ -12,7 +13,7 @@ export const Layout = ({ children }: { children: React.ReactElement }) => {
|
||||
<img src={user?.profile?.image} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="content">{children}</div>
|
||||
<div className="content">{<Outlet />}</div>
|
||||
<div className="footer">
|
||||
made with 💜 by{' '}
|
||||
<a href="https://njump.me/npub1klr0dy2ul2dx9llk58czvpx73rprcmrvd5dc7ck8esg8f8es06qs427gxc">florian</a>
|
||||
|
@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.server {
|
||||
@apply bg-neutral-800 text-neutral-300 rounded-lg p-4 gap-4 cursor-pointer hover:bg-neutral-700 flex flex-row items-center;
|
||||
@apply bg-neutral-800 text-neutral-300 rounded-lg p-4 gap-4 flex flex-row items-center;
|
||||
}
|
||||
|
||||
.server.selected {
|
||||
|
@ -27,7 +27,10 @@ export const ServerList = ({ servers, selectedServer, setSelectedServer, onTrans
|
||||
<div className="server-list">
|
||||
{servers.map((server, sx) => (
|
||||
<div
|
||||
className={`server ${selectedServer == server.name ? 'selected' : ''}`}
|
||||
className={
|
||||
`server ${selectedServer == server.name ? 'selected' : ''} ` +
|
||||
`${setSelectedServer ? ' hover:bg-neutral-700 cursor-pointer' : ''} `
|
||||
}
|
||||
key={sx}
|
||||
onClick={() => setSelectedServer && setSelectedServer(server.name)}
|
||||
>
|
||||
|
@ -1,54 +0,0 @@
|
||||
.message {
|
||||
@apply text-3xl text-center text-white p-12;
|
||||
}
|
||||
|
||||
.message svg {
|
||||
@apply w-8 inline;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
@apply bg-white text-lg text-black hover:bg-pink-600 hover:text-white inline-block rounded-lg p-2 pr-4 pl-3;
|
||||
}
|
||||
|
||||
.action-button svg {
|
||||
@apply w-5 inline align-text-bottom;
|
||||
}
|
||||
|
||||
.error-log {
|
||||
@apply bg-neutral-800 p-4 text-neutral-300 rounded-lg;
|
||||
}
|
||||
|
||||
.error-log svg {
|
||||
@apply w-5 inline align-text-bottom mr-1;
|
||||
}
|
||||
|
||||
.error-log div {
|
||||
@apply p-1 hover:bg-neutral-700 rounded-md grid pr-4;
|
||||
grid-template-columns: 2em auto 6em 10em 7em 1em;
|
||||
}
|
||||
.error-log div span {
|
||||
@apply overflow-ellipsis overflow-hidden text-nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.blob-list svg {
|
||||
@apply w-5 inline align-text-bottom mr-1;
|
||||
}
|
||||
|
||||
.blob-list a {
|
||||
@apply text-pink-500 hover:text-white;
|
||||
}
|
||||
|
||||
.blob-list {
|
||||
}
|
||||
|
||||
.blob-list .blob {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.blob-list .blob a {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
@ -1,171 +0,0 @@
|
||||
import { ArrowDownOnSquareIcon, ArrowUpOnSquareIcon, CheckBadgeIcon, DocumentIcon } from '@heroicons/react/24/outline';
|
||||
import { ServerList } from '../ServerList/ServerList';
|
||||
import { useServerInfo } from '../../utils/useServerInfo';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
|
||||
import { useNDK } from '../../ndk';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { formatFileSize } from '../../utils';
|
||||
import BlobList from '../BlobList/BlobList';
|
||||
import './Transfer.css';
|
||||
|
||||
type TransferStatus = {
|
||||
[key: string]: {
|
||||
sha256: string;
|
||||
status: 'pending' | 'done' | 'error';
|
||||
message?: string;
|
||||
size: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const Transfer = ({ transferSource, onCancel }: { transferSource: string; onCancel?: () => void }) => {
|
||||
const serverInfo = useServerInfo();
|
||||
const [transferTarget, setTransferTarget] = useState<string | undefined>();
|
||||
const { signEventTemplate } = useNDK();
|
||||
const queryClient = useQueryClient();
|
||||
const [started, setStarted] = useState(false);
|
||||
|
||||
const [transferLog, setTransferLog] = useState<TransferStatus>({});
|
||||
|
||||
const closeTransferMode = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blobs', transferTarget] });
|
||||
setTransferTarget(undefined);
|
||||
setTransferLog({});
|
||||
setStarted(false);
|
||||
onCancel && onCancel();
|
||||
};
|
||||
|
||||
const transferJobs = useMemo(() => {
|
||||
if (transferSource && transferTarget) {
|
||||
const sourceBlobs = serverInfo[transferSource].blobs;
|
||||
const targetBlobs = serverInfo[transferTarget].blobs;
|
||||
return sourceBlobs?.filter(src => targetBlobs?.find(tgt => tgt.sha256 == src.sha256) == undefined);
|
||||
}
|
||||
return [];
|
||||
}, [serverInfo, transferSource, transferTarget]);
|
||||
// https://github.com/sindresorhus/p-limit
|
||||
//
|
||||
const performTransfer = async (sourceServer: string, targetServer: string, blobs: BlobDescriptor[]) => {
|
||||
setTransferLog({});
|
||||
setStarted(true);
|
||||
for (const b of blobs) {
|
||||
try {
|
||||
// BlossomClient.getGetAuth()
|
||||
setTransferLog(ts => ({ ...ts, [b.sha256]: { sha256: b.sha256, status: 'pending', size: b.size } }));
|
||||
|
||||
const data = await BlossomClient.getBlob(serverInfo[sourceServer].url, b.sha256);
|
||||
const file = new File([data], b.sha256, { type: b.type, lastModified: b.created });
|
||||
const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob');
|
||||
await BlossomClient.uploadBlob(serverInfo[targetServer].url, file, uploadAuth);
|
||||
setTransferLog(ts => ({ ...ts, [b.sha256]: { sha256: b.sha256, status: 'done', size: b.size } }));
|
||||
} catch (e) {
|
||||
setTransferLog(ts => ({
|
||||
...ts,
|
||||
[b.sha256]: { sha256: b.sha256, status: 'error', message: (e as Error).message, size: blobs.length },
|
||||
}));
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
// if (Object.values(transferLog).filter(b => b.status == 'error').length == 0) {
|
||||
// closeTransferMode();
|
||||
// }
|
||||
};
|
||||
|
||||
const transferStatus = useMemo(() => {
|
||||
const stats = Object.values(transferLog).reduce(
|
||||
(acc, t) => {
|
||||
if (t.status === 'done') {
|
||||
acc.done += 1;
|
||||
} else if (t.status === 'error') {
|
||||
acc.error += 1;
|
||||
} else {
|
||||
acc.pending += 1;
|
||||
}
|
||||
acc.size += t.size;
|
||||
return acc;
|
||||
},
|
||||
{ pending: 0, done: 0, error: 0, size: 0 }
|
||||
);
|
||||
return { ...stats, fullSize: transferJobs?.reduce((acc, b) => acc + b.size, 0) || 0 };
|
||||
}, [transferLog, transferJobs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>
|
||||
<ArrowUpOnSquareIcon /> Transfer Source
|
||||
</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo).filter(s => s.name == transferSource)}
|
||||
onCancel={() => closeTransferMode()}
|
||||
></ServerList>
|
||||
<h2>
|
||||
<ArrowDownOnSquareIcon /> Transfer Target
|
||||
</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo)
|
||||
.filter(s => s.name != transferSource)
|
||||
.sort()}
|
||||
selectedServer={transferTarget}
|
||||
setSelectedServer={setTransferTarget}
|
||||
></ServerList>
|
||||
{transferTarget && transferJobs && transferJobs.length > 0 ? (
|
||||
<>
|
||||
<div className="message">
|
||||
{transferJobs.length} object{transferJobs.length > 1 ? 's' : ''} to transfer{' '}
|
||||
{!started && (
|
||||
<button
|
||||
className="action-button"
|
||||
onClick={() => performTransfer(transferSource, transferTarget, transferJobs)}
|
||||
>
|
||||
<ArrowUpOnSquareIcon />
|
||||
Start
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full bg-gray-200 rounded-lg dark:bg-neutral-800">
|
||||
<div
|
||||
className="bg-pink-600 text-sm font-medium text-pink-100 text-center p-1 leading-none rounded-lg"
|
||||
style={{ width: `${Math.floor((transferStatus.size * 100) / transferStatus.fullSize)}%` }}
|
||||
>
|
||||
{Math.floor((transferStatus.size * 100) / transferStatus.fullSize)} %
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
<div className="message">
|
||||
{formatFileSize(transferStatus.size)} / {formatFileSize(transferStatus.fullSize)} transfered.
|
||||
</div>
|
||||
}
|
||||
<div className="error-log">
|
||||
{Object.values(transferLog)
|
||||
.filter(b => b.status == 'error')
|
||||
.map(t => (
|
||||
<div>
|
||||
<span>
|
||||
<DocumentIcon />
|
||||
</span>
|
||||
<span>{t.sha256}</span>
|
||||
<span>{formatFileSize(t.size)}</span>
|
||||
<span>{t.status && `${t.status}`}</span>
|
||||
<span>{t.message}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{!started && <BlobList blobs={transferJobs}></BlobList>}
|
||||
</>
|
||||
) : (
|
||||
<div className="message">
|
||||
{transferTarget ? (
|
||||
<>
|
||||
<CheckBadgeIcon /> no missing objects to transfer
|
||||
</>
|
||||
) : (
|
||||
<>choose a transfer target above</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
16
src/main.tsx
16
src/main.tsx
@ -1,18 +1,30 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { NDKContextProvider } from './ndk.tsx';
|
||||
import { Route, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom';
|
||||
import { Layout } from './components/Layout/Layout.tsx';
|
||||
import Home from './pages/Home.tsx';
|
||||
import { Transfer } from './pages/Transfer.tsx';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const router = createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/transfer/:source" element={<Transfer />} />
|
||||
</Route>
|
||||
)
|
||||
);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NDKContextProvider>
|
||||
<App />
|
||||
<RouterProvider router={router} />
|
||||
</NDKContextProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import './App.css';
|
||||
import './Home.css';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
|
||||
import { useNDK } from './ndk';
|
||||
import BlobList from './components/BlobList/BlobList';
|
||||
import { useServerInfo } from './utils/useServerInfo';
|
||||
import { ServerList } from './components/ServerList/ServerList';
|
||||
import { Layout } from './components/Layout/Layout';
|
||||
import { Transfer } from './components/Transfer/Transfer';
|
||||
import { useNDK } from '../ndk';
|
||||
import BlobList from '../components/BlobList/BlobList';
|
||||
import { useServerInfo } from '../utils/useServerInfo';
|
||||
import { ServerList } from '../components/ServerList/ServerList';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
/* BOUQUET Blob Organizer Update Quality Use Enhancement Tool */
|
||||
|
||||
// TODOs
|
||||
/*
|
||||
- multi threaded sync
|
||||
- Add server and pulbish list event
|
||||
- upload to single/multi servers
|
||||
- upload exif data removal
|
||||
- upload image resize
|
||||
@ -21,11 +21,11 @@ import { Transfer } from './components/Transfer/Transfer';
|
||||
- thumbnail gallery
|
||||
- check blobs (download & sha256 sum check), maybe limit max size
|
||||
*/
|
||||
function App() {
|
||||
function Home() {
|
||||
const { loginWithExtension, signEventTemplate } = useNDK();
|
||||
const [selectedServer, setSelectedServer] = useState<string | undefined>();
|
||||
const [transferSource, setTransferSource] = useState<string | undefined>();
|
||||
const serverInfo = useServerInfo();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
loginWithExtension();
|
||||
@ -79,38 +79,32 @@ function App() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{transferSource ? (
|
||||
<Transfer transferSource={transferSource} onCancel={() => setTransferSource(undefined)} />
|
||||
) : (
|
||||
<>
|
||||
<h2>Servers</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo).sort()}
|
||||
selectedServer={selectedServer}
|
||||
setSelectedServer={setSelectedServer}
|
||||
onTransfer={server => setTransferSource(server)}
|
||||
></ServerList>
|
||||
<>
|
||||
<h2>Servers</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo).sort()}
|
||||
selectedServer={selectedServer}
|
||||
setSelectedServer={setSelectedServer}
|
||||
onTransfer={() => navigate('/transfer/' + selectedServer)}
|
||||
></ServerList>
|
||||
|
||||
{selectedServer && serverInfo[selectedServer] && selectedServerBlobs && (
|
||||
<>
|
||||
<h2>Your objects on {serverInfo[selectedServer].name}</h2>
|
||||
<BlobList
|
||||
blobs={selectedServerBlobs}
|
||||
onDelete={blob =>
|
||||
deleteBlob.mutate({
|
||||
serverName: serverInfo[selectedServer].name,
|
||||
serverUrl: serverInfo[selectedServer].url,
|
||||
hash: blob.sha256,
|
||||
})
|
||||
}
|
||||
></BlobList>
|
||||
</>
|
||||
)}
|
||||
{selectedServer && serverInfo[selectedServer] && selectedServerBlobs && (
|
||||
<>
|
||||
<h2>Your objects on {serverInfo[selectedServer].name}</h2>
|
||||
<BlobList
|
||||
blobs={selectedServerBlobs}
|
||||
onDelete={blob =>
|
||||
deleteBlob.mutate({
|
||||
serverName: serverInfo[selectedServer].name,
|
||||
serverUrl: serverInfo[selectedServer].url,
|
||||
hash: blob.sha256,
|
||||
})
|
||||
}
|
||||
></BlobList>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default Home;
|
49
src/pages/Transfer.css
Normal file
49
src/pages/Transfer.css
Normal file
@ -0,0 +1,49 @@
|
||||
.message {
|
||||
@apply text-3xl text-center text-white p-12;
|
||||
}
|
||||
|
||||
.message svg {
|
||||
@apply w-8 inline;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
@apply bg-white text-lg text-black hover:bg-pink-600 hover:text-white inline-block rounded-lg p-2 pr-4 pl-3;
|
||||
}
|
||||
|
||||
.action-button svg {
|
||||
@apply w-5 inline align-text-bottom;
|
||||
}
|
||||
|
||||
.error-log {
|
||||
@apply bg-neutral-800 p-4 text-neutral-300 rounded-lg;
|
||||
}
|
||||
|
||||
.error-log svg {
|
||||
@apply w-5 inline align-text-bottom mr-1;
|
||||
}
|
||||
|
||||
.error-log div {
|
||||
@apply p-1 hover:bg-neutral-700 rounded-md grid pr-4;
|
||||
grid-template-columns: 2em auto 6em 2em 15em;
|
||||
}
|
||||
.error-log div span {
|
||||
@apply overflow-ellipsis overflow-hidden text-nowrap;
|
||||
}
|
||||
|
||||
.blob-list svg {
|
||||
@apply w-5 inline align-text-bottom mr-1;
|
||||
}
|
||||
|
||||
.blob-list a {
|
||||
@apply text-pink-500 hover:text-white;
|
||||
}
|
||||
|
||||
.blob-list {
|
||||
}
|
||||
|
||||
.blob-list .blob {
|
||||
}
|
||||
|
||||
.blob-list .blob a {
|
||||
@apply cursor-pointer;
|
||||
}
|
182
src/pages/Transfer.tsx
Normal file
182
src/pages/Transfer.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import {
|
||||
ArrowDownOnSquareIcon,
|
||||
ArrowUpOnSquareIcon,
|
||||
CheckBadgeIcon,
|
||||
DocumentIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { ServerList } from '../components/ServerList/ServerList';
|
||||
import { useServerInfo } from '../utils/useServerInfo';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
|
||||
import { useNDK } from '../ndk';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { formatFileSize } from '../utils';
|
||||
import BlobList from '../components/BlobList/BlobList';
|
||||
import './Transfer.css';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
type TransferStatus = {
|
||||
[key: string]: {
|
||||
sha256: string;
|
||||
status: 'pending' | 'done' | 'error';
|
||||
message?: string;
|
||||
size: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const Transfer = () => {
|
||||
const { source: transferSource } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const serverInfo = useServerInfo();
|
||||
const [transferTarget, setTransferTarget] = useState<string | undefined>();
|
||||
const { signEventTemplate } = useNDK();
|
||||
const queryClient = useQueryClient();
|
||||
const [started, setStarted] = useState(false);
|
||||
|
||||
const [transferLog, setTransferLog] = useState<TransferStatus>({});
|
||||
|
||||
const closeTransferMode = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blobs', transferTarget] });
|
||||
setTransferTarget(undefined);
|
||||
setTransferLog({});
|
||||
setStarted(false);
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const transferJobs = useMemo(() => {
|
||||
if (transferSource && transferTarget) {
|
||||
const sourceBlobs = serverInfo[transferSource].blobs;
|
||||
const targetBlobs = serverInfo[transferTarget].blobs;
|
||||
return sourceBlobs?.filter(src => targetBlobs?.find(tgt => tgt.sha256 == src.sha256) == undefined);
|
||||
}
|
||||
return [];
|
||||
}, [serverInfo, transferSource, transferTarget]);
|
||||
// https://github.com/sindresorhus/p-limit
|
||||
//
|
||||
const performTransfer = async (sourceServer: string, targetServer: string, blobs: BlobDescriptor[]) => {
|
||||
setTransferLog({});
|
||||
setStarted(true);
|
||||
for (const b of blobs) {
|
||||
try {
|
||||
// BlossomClient.getGetAuth()
|
||||
setTransferLog(ts => ({ ...ts, [b.sha256]: { sha256: b.sha256, status: 'pending', size: b.size } }));
|
||||
|
||||
const data = await BlossomClient.getBlob(serverInfo[sourceServer].url, b.sha256);
|
||||
const file = new File([data], b.sha256, { type: b.type, lastModified: b.created });
|
||||
const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob');
|
||||
await BlossomClient.uploadBlob(serverInfo[targetServer].url, file, uploadAuth);
|
||||
setTransferLog(ts => ({ ...ts, [b.sha256]: { sha256: b.sha256, status: 'done', size: b.size } }));
|
||||
} catch (e) {
|
||||
setTransferLog(ts => ({
|
||||
...ts,
|
||||
[b.sha256]: { sha256: b.sha256, status: 'error', message: (e as Error).message, size: blobs.length },
|
||||
}));
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
// if (Object.values(transferLog).filter(b => b.status == 'error').length == 0) {
|
||||
// closeTransferMode();
|
||||
// }
|
||||
};
|
||||
|
||||
const transferStatus = useMemo(() => {
|
||||
const stats = Object.values(transferLog).reduce(
|
||||
(acc, t) => {
|
||||
if (t.status === 'done') {
|
||||
acc.done += 1;
|
||||
} else if (t.status === 'error') {
|
||||
acc.error += 1;
|
||||
} else {
|
||||
acc.pending += 1;
|
||||
}
|
||||
acc.size += t.status == 'done' ? t.size : 0;
|
||||
return acc;
|
||||
},
|
||||
{ pending: 0, done: 0, error: 0, size: 0 }
|
||||
);
|
||||
return { ...stats, fullSize: transferJobs?.reduce((acc, b) => acc + b.size, 0) || 0 };
|
||||
}, [transferLog, transferJobs]);
|
||||
|
||||
return (
|
||||
transferSource && (
|
||||
<>
|
||||
<h2>
|
||||
<ArrowUpOnSquareIcon /> Transfer Source
|
||||
</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo).filter(s => s.name == transferSource)}
|
||||
onCancel={() => closeTransferMode()}
|
||||
></ServerList>
|
||||
<h2>
|
||||
<ArrowDownOnSquareIcon /> Transfer Target
|
||||
</h2>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo)
|
||||
.filter(s => s.name != transferSource)
|
||||
.sort()}
|
||||
selectedServer={transferTarget}
|
||||
setSelectedServer={setTransferTarget}
|
||||
></ServerList>
|
||||
{transferTarget && transferJobs && transferJobs.length > 0 ? (
|
||||
<>
|
||||
<div className="message">
|
||||
{transferJobs.length} object{transferJobs.length > 1 ? 's' : ''} to transfer{' '}
|
||||
{!started && (
|
||||
<button
|
||||
className="action-button"
|
||||
onClick={() => performTransfer(transferSource, transferTarget, transferJobs)}
|
||||
>
|
||||
<ArrowUpOnSquareIcon />
|
||||
Start
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full bg-gray-200 rounded-lg dark:bg-neutral-800">
|
||||
<div
|
||||
className="bg-pink-600 text-sm font-medium text-pink-100 text-center p-1 leading-none rounded-lg"
|
||||
style={{ width: `${Math.floor((transferStatus.size * 100) / transferStatus.fullSize)}%` }}
|
||||
>
|
||||
{Math.floor((transferStatus.size * 100) / transferStatus.fullSize)} %
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
<div className="message">
|
||||
{formatFileSize(transferStatus.size)} / {formatFileSize(transferStatus.fullSize)} transferred
|
||||
</div>
|
||||
}
|
||||
<div className="error-log">
|
||||
{Object.values(transferLog)
|
||||
.filter(b => b.status == 'error')
|
||||
.map(t => (
|
||||
<div>
|
||||
<span>
|
||||
<DocumentIcon />
|
||||
</span>
|
||||
<span>{t.sha256}</span>
|
||||
<span>{formatFileSize(t.size)}</span>
|
||||
<span>{t.status && (t.status == 'error' ? <ExclamationTriangleIcon /> : '')}</span>
|
||||
<span>{t.message}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{!started && <BlobList blobs={transferJobs}></BlobList>}
|
||||
</>
|
||||
) : (
|
||||
<div className="message">
|
||||
{transferTarget ? (
|
||||
<>
|
||||
<CheckBadgeIcon /> no missing objects to transfer
|
||||
</>
|
||||
) : (
|
||||
<>choose a transfer target above</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user