feat: improve file list
This commit is contained in:
parent
0d8686a850
commit
5fbe40faae
@ -1,11 +1,11 @@
|
|||||||
use crate::auth::nip98::Nip98Auth;
|
use crate::auth::nip98::Nip98Auth;
|
||||||
use crate::db::{Database, FileUpload};
|
use crate::db::{Database, FileUpload, User};
|
||||||
use crate::routes::{Nip94Event, PagedResult};
|
use crate::routes::{Nip94Event, PagedResult};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
use rocket::{routes, Responder, Route, State};
|
use rocket::{routes, Responder, Route, State};
|
||||||
use sqlx::{Error, Row};
|
use sqlx::{Error, QueryBuilder, Row};
|
||||||
|
|
||||||
pub fn admin_routes() -> Vec<Route> {
|
pub fn admin_routes() -> Vec<Route> {
|
||||||
routes![admin_list_files, admin_get_self]
|
routes![admin_list_files, admin_get_self]
|
||||||
@ -55,6 +55,13 @@ pub struct SelfUser {
|
|||||||
pub total_size: u64,
|
pub total_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AdminNip94File {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub inner: Nip94Event,
|
||||||
|
pub uploader: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[rocket::get("/self")]
|
#[rocket::get("/self")]
|
||||||
async fn admin_get_self(auth: Nip98Auth, db: &State<Database>) -> AdminResponse<SelfUser> {
|
async fn admin_get_self(auth: Nip98Auth, db: &State<Database>) -> AdminResponse<SelfUser> {
|
||||||
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
||||||
@ -76,14 +83,15 @@ async fn admin_get_self(auth: Nip98Auth, db: &State<Database>) -> AdminResponse<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::get("/files?<page>&<count>")]
|
#[rocket::get("/files?<page>&<count>&<mime_type>")]
|
||||||
async fn admin_list_files(
|
async fn admin_list_files(
|
||||||
auth: Nip98Auth,
|
auth: Nip98Auth,
|
||||||
page: u32,
|
page: u32,
|
||||||
count: u32,
|
count: u32,
|
||||||
|
mime_type: Option<String>,
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
settings: &State<Settings>,
|
settings: &State<Settings>,
|
||||||
) -> AdminResponse<PagedResult<Nip94Event>> {
|
) -> AdminResponse<PagedResult<AdminNip94File>> {
|
||||||
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
||||||
let server_count = count.clamp(1, 5_000);
|
let server_count = count.clamp(1, 5_000);
|
||||||
|
|
||||||
@ -95,14 +103,20 @@ async fn admin_list_files(
|
|||||||
if !user.is_admin {
|
if !user.is_admin {
|
||||||
return AdminResponse::error("User is not an admin");
|
return AdminResponse::error("User is not an admin");
|
||||||
}
|
}
|
||||||
match db.list_all_files(page * server_count, server_count).await {
|
match db
|
||||||
|
.list_all_files(page * server_count, server_count, mime_type)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok((files, count)) => AdminResponse::success(PagedResult {
|
Ok((files, count)) => AdminResponse::success(PagedResult {
|
||||||
count: files.len() as u32,
|
count: files.len() as u32,
|
||||||
page,
|
page,
|
||||||
total: count as u32,
|
total: count as u32,
|
||||||
files: files
|
files: files
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|f| Nip94Event::from_upload(settings, f))
|
.map(|f| AdminNip94File {
|
||||||
|
inner: Nip94Event::from_upload(settings, &f.0),
|
||||||
|
uploader: f.1.into_iter().map(|u| hex::encode(&u.pubkey)).collect(),
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}),
|
}),
|
||||||
Err(e) => AdminResponse::error(&format!("Could not list files: {}", e)),
|
Err(e) => AdminResponse::error(&format!("Could not list files: {}", e)),
|
||||||
@ -114,21 +128,29 @@ impl Database {
|
|||||||
&self,
|
&self,
|
||||||
offset: u32,
|
offset: u32,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
) -> Result<(Vec<FileUpload>, i64), Error> {
|
mime_type: Option<String>,
|
||||||
let results: Vec<FileUpload> = sqlx::query_as(
|
) -> Result<(Vec<(FileUpload, Vec<User>)>, i64), Error> {
|
||||||
"select u.* \
|
let mut q = QueryBuilder::new("select u.* from uploads u ");
|
||||||
from uploads u \
|
if let Some(m) = mime_type {
|
||||||
order by u.created desc \
|
q.push("where u.mime_type = ");
|
||||||
limit ? offset ?",
|
q.push_bind(m);
|
||||||
)
|
}
|
||||||
.bind(limit)
|
q.push(" order by u.created desc limit ");
|
||||||
.bind(offset)
|
q.push_bind(limit);
|
||||||
.fetch_all(&self.pool)
|
q.push(" offset ");
|
||||||
.await?;
|
q.push_bind(offset);
|
||||||
|
|
||||||
|
let results: Vec<FileUpload> = q.build_query_as().fetch_all(&self.pool).await?;
|
||||||
let count: i64 = sqlx::query("select count(u.id) from uploads u")
|
let count: i64 = sqlx::query("select count(u.id) from uploads u")
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await?
|
.await?
|
||||||
.try_get(0)?;
|
.try_get(0)?;
|
||||||
Ok((results, count))
|
|
||||||
|
let mut res = Vec::with_capacity(results.len());
|
||||||
|
for upload in results.into_iter() {
|
||||||
|
let upd = self.get_file_owners(&upload.id).await?;
|
||||||
|
res.push((upload, upd));
|
||||||
|
}
|
||||||
|
Ok((res, count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,32 @@ import { hexToBech32 } from "@snort/shared";
|
|||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
export default function Profile({ link }: { link: NostrLink }) {
|
export default function Profile({
|
||||||
|
link,
|
||||||
|
size,
|
||||||
|
showName,
|
||||||
|
}: {
|
||||||
|
link: NostrLink;
|
||||||
|
size?: number;
|
||||||
|
showName?: boolean;
|
||||||
|
}) {
|
||||||
const profile = useUserProfile(link.id);
|
const profile = useUserProfile(link.id);
|
||||||
|
const s = size ?? 40;
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<img
|
<img
|
||||||
src={profile?.picture}
|
src={profile?.picture}
|
||||||
className="rounded-full w-12 h-12 object-fit object-center"
|
width={s}
|
||||||
|
height={s}
|
||||||
|
className="rounded-full object-fit object-center"
|
||||||
/>
|
/>
|
||||||
<div>
|
{(showName ?? true) && (
|
||||||
{profile?.display_name ??
|
<div>
|
||||||
profile?.name ??
|
{profile?.display_name ??
|
||||||
hexToBech32("npub", link.id).slice(0, 12)}
|
profile?.name ??
|
||||||
</div>
|
hexToBech32("npub", link.id).slice(0, 12)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,5 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@apply border-neutral-500
|
@apply border-neutral-500;
|
||||||
}
|
}
|
||||||
|
174964
ui_src/src/report.json
174964
ui_src/src/report.json
File diff suppressed because one or more lines are too long
@ -2,7 +2,11 @@ import { base64 } from "@scure/base";
|
|||||||
import { throwIfOffline } from "@snort/shared";
|
import { throwIfOffline } from "@snort/shared";
|
||||||
import { EventKind, EventPublisher, NostrEvent } from "@snort/system";
|
import { EventKind, EventPublisher, NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
export interface AdminSelf { is_admin: boolean, file_count: number, total_size: number }
|
export interface AdminSelf {
|
||||||
|
is_admin: boolean;
|
||||||
|
file_count: number;
|
||||||
|
total_size: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Route96 {
|
export class Route96 {
|
||||||
constructor(
|
constructor(
|
||||||
@ -14,8 +18,7 @@ export class Route96 {
|
|||||||
|
|
||||||
async getSelf() {
|
async getSelf() {
|
||||||
const rsp = await this.#req("admin/self", "GET");
|
const rsp = await this.#req("admin/self", "GET");
|
||||||
const data =
|
const data = await this.#handleResponse<AdminResponse<AdminSelf>>(rsp);
|
||||||
await this.#handleResponse<AdminResponse<AdminSelf>>(rsp);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +51,16 @@ export class Blossom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async mirror(url: string) {
|
async mirror(url: string) {
|
||||||
const rsp = await this.#req("mirror", "PUT", "mirror", JSON.stringify({ url }), undefined, {
|
const rsp = await this.#req(
|
||||||
"content-type": "application/json"
|
"mirror",
|
||||||
});
|
"PUT",
|
||||||
|
"mirror",
|
||||||
|
JSON.stringify({ url }),
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
);
|
||||||
if (rsp.ok) {
|
if (rsp.ok) {
|
||||||
return (await rsp.json()) as BlobDescriptor;
|
return (await rsp.json()) as BlobDescriptor;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent, NostrLink } from "@snort/system";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormatBytes } from "../const";
|
import { FormatBytes } from "../const";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import Profile from "../components/profile";
|
||||||
|
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@ -9,6 +10,7 @@ interface FileInfo {
|
|||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
uploader?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileList({
|
export default function FileList({
|
||||||
@ -30,9 +32,17 @@ export default function FileList({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderInner(f: FileInfo) {
|
function renderInner(f: FileInfo) {
|
||||||
if (f.type?.startsWith("image/") || f.type?.startsWith("video/") || !f.type) {
|
if (
|
||||||
|
f.type?.startsWith("image/") ||
|
||||||
|
f.type?.startsWith("video/") ||
|
||||||
|
!f.type
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<img src={f.url.replace(`/${f.id}`, `/thumb/${f.id}`)} className="w-full h-full object-contain object-center" loading="lazy" />
|
<img
|
||||||
|
src={f.url.replace(`/${f.id}`, `/thumb/${f.id}`)}
|
||||||
|
className="w-full h-full object-contain object-center"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +58,7 @@ export default function FileList({
|
|||||||
name: f.content,
|
name: f.content,
|
||||||
type: f.tags.find((a) => a[0] === "m")?.at(1),
|
type: f.tags.find((a) => a[0] === "m")?.at(1),
|
||||||
size: Number(f.tags.find((a) => a[0] === "size")?.at(1)),
|
size: Number(f.tags.find((a) => a[0] === "size")?.at(1)),
|
||||||
|
uploader: "uploader" in f ? (f.uploader as Array<string>) : undefined,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -68,12 +79,14 @@ export default function FileList({
|
|||||||
ret.push(
|
ret.push(
|
||||||
<div
|
<div
|
||||||
onClick={() => onPage?.(x)}
|
onClick={() => onPage?.(x)}
|
||||||
className={classNames("bg-neutral-700 hover:bg-neutral-600 min-w-8 text-center cursor-pointer font-bold",
|
className={classNames(
|
||||||
|
"bg-neutral-700 hover:bg-neutral-600 min-w-8 text-center cursor-pointer font-bold",
|
||||||
{
|
{
|
||||||
"rounded-l-md": x === start,
|
"rounded-l-md": x === start,
|
||||||
"rounded-r-md": (x + 1) === n,
|
"rounded-r-md": x + 1 === n,
|
||||||
"bg-neutral-400": page === x,
|
"bg-neutral-400": page === x,
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{x + 1}
|
{x + 1}
|
||||||
</div>,
|
</div>,
|
||||||
@ -96,24 +109,39 @@ export default function FileList({
|
|||||||
>
|
>
|
||||||
<div className="absolute flex flex-col items-center justify-center w-full h-full text-wrap text-sm break-all text-center opacity-0 hover:opacity-100 hover:bg-black/80">
|
<div className="absolute flex flex-col items-center justify-center w-full h-full text-wrap text-sm break-all text-center opacity-0 hover:opacity-100 hover:bg-black/80">
|
||||||
<div>
|
<div>
|
||||||
{(info.name?.length ?? 0) === 0 ? "Untitled" : info.name}
|
{(info.name?.length ?? 0) === 0
|
||||||
|
? "Untitled"
|
||||||
|
: info.name!.length > 20
|
||||||
|
? `${info.name?.substring(0, 10)}...${info.name?.substring(info.name.length - 10)}`
|
||||||
|
: info.name}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{info.size && !isNaN(info.size)
|
{info.size && !isNaN(info.size)
|
||||||
? FormatBytes(info.size, 2)
|
? FormatBytes(info.size, 2)
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
<div>{info.type}</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<a href={info.url} className="underline" target="_blank">
|
<a href={info.url} className="underline" target="_blank">
|
||||||
Link
|
Link
|
||||||
</a>
|
</a>
|
||||||
{onDelete && <a href="#" onClick={e => {
|
{onDelete && (
|
||||||
e.preventDefault();
|
<a
|
||||||
onDelete?.(info.id)
|
href="#"
|
||||||
}} className="underline">
|
onClick={(e) => {
|
||||||
Delete
|
e.preventDefault();
|
||||||
</a>}
|
onDelete?.(info.id);
|
||||||
|
}}
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{info.uploader &&
|
||||||
|
info.uploader.map((a) => (
|
||||||
|
<Profile link={NostrLink.publicKey(a)} size={20} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{renderInner(info)}
|
{renderInner(info)}
|
||||||
</div>
|
</div>
|
||||||
@ -128,6 +156,9 @@ export default function FileList({
|
|||||||
<table className="table-auto text-sm">
|
<table className="table-auto text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
||||||
|
Preview
|
||||||
|
</th>
|
||||||
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
@ -137,6 +168,11 @@ export default function FileList({
|
|||||||
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
||||||
Size
|
Size
|
||||||
</th>
|
</th>
|
||||||
|
{files.some((i) => "uploader" in i) && (
|
||||||
|
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
||||||
|
Uploader
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
<th className="border border-neutral-400 bg-neutral-500 py-1 px-2">
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
@ -147,6 +183,9 @@ export default function FileList({
|
|||||||
const info = getInfo(a);
|
const info = getInfo(a);
|
||||||
return (
|
return (
|
||||||
<tr key={info.id}>
|
<tr key={info.id}>
|
||||||
|
<td className="border border-neutral-500 py-1 px-2 w-8 h-8">
|
||||||
|
{renderInner(info)}
|
||||||
|
</td>
|
||||||
<td className="border border-neutral-500 py-1 px-2 break-all">
|
<td className="border border-neutral-500 py-1 px-2 break-all">
|
||||||
{(info.name?.length ?? 0) === 0 ? "<Untitled>" : info.name}
|
{(info.name?.length ?? 0) === 0 ? "<Untitled>" : info.name}
|
||||||
</td>
|
</td>
|
||||||
@ -158,17 +197,30 @@ export default function FileList({
|
|||||||
? FormatBytes(info.size, 2)
|
? FormatBytes(info.size, 2)
|
||||||
: ""}
|
: ""}
|
||||||
</td>
|
</td>
|
||||||
|
{info.uploader && (
|
||||||
|
<td className="border border-neutral-500 py-1 px-2">
|
||||||
|
{info.uploader.map((a) => (
|
||||||
|
<Profile link={NostrLink.publicKey(a)} size={20} />
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
<td className="border border-neutral-500 py-1 px-2">
|
<td className="border border-neutral-500 py-1 px-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<a href={info.url} className="underline" target="_blank">
|
<a href={info.url} className="underline" target="_blank">
|
||||||
Link
|
Link
|
||||||
</a>
|
</a>
|
||||||
{onDelete && <a href="#" onClick={e => {
|
{onDelete && (
|
||||||
e.preventDefault();
|
<a
|
||||||
onDelete?.(info.id)
|
href="#"
|
||||||
}} className="underline">
|
onClick={(e) => {
|
||||||
Delete
|
e.preventDefault();
|
||||||
</a>}
|
onDelete?.(info.id);
|
||||||
|
}}
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -30,7 +30,8 @@ export default function Upload() {
|
|||||||
const legacyFiles = Report as Record<string, Array<string>>;
|
const legacyFiles = Report as Record<string, Array<string>>;
|
||||||
const myLegacyFiles = login ? (legacyFiles[login.pubkey] ?? []) : [];
|
const myLegacyFiles = login ? (legacyFiles[login.pubkey] ?? []) : [];
|
||||||
|
|
||||||
const url = import.meta.env.VITE_API_URL || `${location.protocol}//${location.host}`;
|
const url =
|
||||||
|
import.meta.env.VITE_API_URL || `${location.protocol}//${location.host}`;
|
||||||
async function doUpload() {
|
async function doUpload() {
|
||||||
if (!pub) return;
|
if (!pub) return;
|
||||||
if (!toUpload) return;
|
if (!toUpload) return;
|
||||||
@ -38,7 +39,9 @@ export default function Upload() {
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (type === "blossom") {
|
if (type === "blossom") {
|
||||||
const uploader = new Blossom(url, pub);
|
const uploader = new Blossom(url, pub);
|
||||||
const result = noCompress ? await uploader.upload(toUpload) : await uploader.media(toUpload);
|
const result = noCompress
|
||||||
|
? await uploader.upload(toUpload)
|
||||||
|
: await uploader.media(toUpload);
|
||||||
setResults((s) => [...s, result]);
|
setResults((s) => [...s, result]);
|
||||||
}
|
}
|
||||||
if (type === "nip96") {
|
if (type === "nip96") {
|
||||||
@ -187,29 +190,33 @@ export default function Upload() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={doUpload} disabled={login === undefined}>
|
onClick={doUpload}
|
||||||
|
disabled={login === undefined}
|
||||||
|
>
|
||||||
Upload
|
Upload
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
{!listedFiles && <Button disabled={login === undefined} onClick={() => listUploads(0)}>
|
{!listedFiles && (
|
||||||
List Uploads
|
<Button disabled={login === undefined} onClick={() => listUploads(0)}>
|
||||||
</Button>}
|
List Uploads
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{self && (
|
||||||
{self && <div className="flex justify-between font-medium">
|
<div className="flex justify-between font-medium">
|
||||||
<div>Uploads: {self.file_count.toLocaleString()}</div>
|
<div>Uploads: {self.file_count.toLocaleString()}</div>
|
||||||
<div>Total Size: {FormatBytes(self.total_size)}</div>
|
<div>Total Size: {FormatBytes(self.total_size)}</div>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{login && myLegacyFiles.length > 0 && (
|
{login && myLegacyFiles.length > 0 && (
|
||||||
<div className="flex flex-col gap-4 font-bold">
|
<div className="flex flex-col gap-4 font-bold">
|
||||||
You have {myLegacyFiles.length.toLocaleString()} files which can be migrated from void.cat
|
You have {myLegacyFiles.length.toLocaleString()} files which can be
|
||||||
|
migrated from void.cat
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button onClick={() => migrateLegacy()}>
|
<Button onClick={() => migrateLegacy()}>Migrate Files</Button>
|
||||||
Migrate Files
|
<Button onClick={() => setShowLegacy((s) => !s)}>
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setShowLegacy(s => !s)}>
|
|
||||||
{!showLegacy ? "Show Files" : "Hide Files"}
|
{!showLegacy ? "Show Files" : "Hide Files"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -218,7 +225,10 @@ export default function Upload() {
|
|||||||
)}
|
)}
|
||||||
{showLegacy && (
|
{showLegacy && (
|
||||||
<FileList
|
<FileList
|
||||||
files={myLegacyFiles.map(f => ({ id: f, url: `https://void.cat/d/${f}` }))}
|
files={myLegacyFiles.map((f) => ({
|
||||||
|
id: f,
|
||||||
|
url: `https://void.cat/d/${f}`,
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{listedFiles && (
|
{listedFiles && (
|
||||||
@ -248,8 +258,7 @@ export default function Upload() {
|
|||||||
onDelete={async (x) => {
|
onDelete={async (x) => {
|
||||||
await deleteFile(x);
|
await deleteFile(x);
|
||||||
await listAllUploads(adminListedPage);
|
await listAllUploads(adminListedPage);
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user