BlossomAdminTab-Bu_C13Fn.js raw

   1  import{a as ae,r as a,i as V,j as e,B as h,t as b,n as ce}from"./index-DfKg850Q.js";function m(){return window.location.origin}async function U(s,l){const c=Math.floor(Date.now()/1e3),d=c+60,u=[["t",s],["expiration",d.toString()]];l&&u.push(["x",l]);const f=await V.signer.signEvent({kind:24242,created_at:c,tags:u,content:`Blossom ${s} operation`});return"Nostr "+btoa(JSON.stringify(f))}function S(s){if(!s)return"0 B";const l=["B","KB","MB","GB"];let c=0,d=s;for(;d>=1024&&c<l.length-1;)d/=1024,c++;return`${d.toFixed(c===0?0:1)} ${l[c]}`}function le(s){return s?new Date(s*1e3).toLocaleString():"Unknown"}function K(s){return s?`${s.slice(0,8)}...${s.slice(-8)}`:""}function _(s){try{return ce.npubEncode(s)}catch{return s}}function R(s){return s?`${s.slice(0,12)}...${s.slice(-8)}`:""}function oe(s,l,c){return s.length?[...s].sort((u,f)=>{const i=l==="date"?(u.uploaded||0)-(f.uploaded||0):(u.size||0)-(f.size||0);return c==="desc"?-i:i}):s}function de(s){return s.url?s.url.startsWith("http://")||s.url.startsWith("https://")?s.url:s.url.startsWith("/")?`${m()}${s.url}`:`http://${s.url}`:`${m()}/blossom/${s.sha256}`}function ue(s){const l=de(s),c=l.includes("?")?"&":"?";return`${l}${c}w=128`}function fe(s){return s?s.startsWith("image/")?"image":s.startsWith("video/")?"video":s.startsWith("audio/")?"audio":"file":"unknown"}const k=40;function he(){var q,J;const{isAdmin:s,isOwner:l}=ae(),[c,d]=a.useState([]),[u,f]=a.useState(!1),[i,T]=a.useState(null),[Z,Q]=a.useState([]),[$,W]=a.useState(!1),[g,X]=a.useState("date"),[v,I]=a.useState("desc"),[F,B]=a.useState(k),O=a.useRef(null),[x,z]=a.useState(new Set),[M,P]=a.useState(!1),[N,w]=a.useState(""),E=s||l,D=a.useCallback(async()=>{f(!0),w("");try{const t=await U("admin"),r=`${m()}/blossom/admin/users`,n=await fetch(r,{headers:{Authorization:t}});if(!n.ok)throw new Error(`Failed to load user stats: ${n.statusText}`);const y=(await n.json()).map(j=>({...j}));d(y);for(const j of y)V.fetchProfile(j.pubkey).then(L=>{L&&(j.profile={name:L.username,picture:L.avatar},d(ie=>[...ie]))}).catch(()=>{})}catch(t){w(t instanceof Error?t.message:"Failed to load user stats")}finally{f(!1)}},[]),p=a.useCallback(async t=>{W(!0),w("");try{const r=`${m()}/blossom/list/${t}`,n=await fetch(r);if(!n.ok)throw new Error(`Failed to load blobs: ${n.statusText}`);const o=await n.json();Q(o)}catch(r){w(r instanceof Error?r.message:"Failed to load blobs")}finally{W(!1)}},[]);a.useEffect(()=>{E&&D()},[E,D]),a.useEffect(()=>{i&&(B(k),z(new Set),p(i.pubkey))},[i,p]),a.useEffect(()=>{B(k)},[g,v]),a.useEffect(()=>{const t=O.current;if(!t)return;const r=new IntersectionObserver(n=>{n[0].isIntersecting&&B(o=>o+k)},{rootMargin:"0px 0px 150% 0px"});return r.observe(t),()=>r.disconnect()},[i]);const C=oe(Z,g,v),A=C.slice(0,F),Y=F<C.length,ee=c.reduce((t,r)=>t+(r.total_size_bytes||0),0),te=c.reduce((t,r)=>t+(r.blob_count||0),0),G=t=>{g===t?I(r=>r==="desc"?"asc":"desc"):(X(t),I("desc"))},se=t=>{z(r=>{const n=new Set(r);return n.has(t)?n.delete(t):n.add(t),n})},re=async t=>{if(confirm(`Delete blob ${K(t.sha256)}?`))try{const r=await U("delete",t.sha256),n=`${m()}/blossom/${t.sha256}`,o=await fetch(n,{method:"DELETE",headers:{Authorization:r}});if(!o.ok)throw new Error(`Delete failed: ${o.statusText}`);b.success("Blob deleted"),i&&p(i.pubkey)}catch(r){b.error(r instanceof Error?r.message:"Delete failed")}},ne=async()=>{if(x.size===0||!confirm(`Delete ${x.size} selected file(s)? This cannot be undone.`))return;P(!0);let t=0,r=0;for(const n of x)try{const o=await U("delete",n),y=`${m()}/blossom/${n}`;(await fetch(y,{method:"DELETE",headers:{Authorization:o}})).ok?t++:r++}catch{r++}z(new Set),P(!1),r>0?b.warning(`Deleted ${t}, failed ${r}`):b.success(`Deleted ${t} file(s)`),i&&p(i.pubkey)},H=()=>{i?p(i.pubkey):D()};return E?i?e.jsxs("div",{className:"p-4 space-y-3 w-full",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(h,{variant:"ghost",size:"sm",onClick:()=>T(null),children:"← Back"}),e.jsxs("div",{className:"flex items-center gap-2",children:[((q=i.profile)==null?void 0:q.picture)&&e.jsx("img",{src:i.profile.picture,alt:"",className:"w-6 h-6 rounded-full object-cover"}),e.jsx("span",{className:"font-semibold truncate max-w-[200px]",children:((J=i.profile)==null?void 0:J.name)||R(_(i.pubkey))})]})]}),e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[x.size>0&&e.jsx(h,{variant:"destructive",size:"sm",onClick:ne,disabled:M,children:M?"Deleting...":`Delete Selected (${x.size})`}),e.jsxs(h,{variant:"ghost",size:"sm",onClick:()=>G("date"),children:["Date ",g==="date"?v==="desc"?"↓":"↑":""]}),e.jsxs(h,{variant:"ghost",size:"sm",onClick:()=>G("size"),children:["Size ",g==="size"?v==="desc"?"↓":"↑":""]}),e.jsx(h,{size:"sm",onClick:H,disabled:$,children:$?"Loading...":"Refresh"})]})]}),N&&e.jsx("div",{className:"rounded-md bg-destructive/10 text-destructive p-3 text-sm",children:N}),e.jsxs("div",{className:"text-xs text-muted-foreground",children:[C.length," blob(s) · ",S(i.total_size_bytes)]}),$&&A.length===0?e.jsx("div",{className:"text-center py-8 text-muted-foreground",children:"Loading blobs..."}):A.length===0?e.jsx("div",{className:"text-center py-8 text-muted-foreground",children:"No files found for this user."}):e.jsxs("div",{className:"space-y-1",children:[A.map(t=>{var r,n;return e.jsxs("div",{className:`flex items-center gap-2 rounded-md px-3 py-2 ${x.has(t.sha256)?"bg-primary/10 ring-1 ring-primary/30":"bg-card"}`,children:[e.jsx("input",{type:"checkbox",checked:x.has(t.sha256),onChange:()=>se(t.sha256),className:"shrink-0"}),e.jsx("div",{className:"w-10 h-10 shrink-0 rounded overflow-hidden bg-muted flex items-center justify-center",children:fe(t.type)==="image"?e.jsx("img",{src:ue(t),alt:"",className:"w-full h-full object-cover",loading:"lazy"}):e.jsx("span",{className:"text-xs text-muted-foreground",children:((n=(r=t.type)==null?void 0:r.split("/")[1])==null?void 0:n.slice(0,4))||"?"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"font-mono text-xs truncate",title:t.sha256,children:K(t.sha256)}),e.jsxs("div",{className:"flex flex-wrap gap-2 text-xs text-muted-foreground",children:[e.jsx("span",{children:S(t.size)}),e.jsx("span",{children:t.type||"unknown"}),e.jsx("span",{children:le(t.uploaded)})]})]}),e.jsx(h,{variant:"ghost",size:"sm",className:"shrink-0 text-destructive hover:text-destructive hover:bg-destructive/10",onClick:o=>{o.stopPropagation(),re(t)},children:"Delete"})]},t.sha256)}),Y&&e.jsx("div",{ref:O,className:"text-center py-4 text-xs text-muted-foreground",children:"Loading more..."})]})]}):e.jsxs("div",{className:"p-4 space-y-3 w-full",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Blossom Storage Admin"}),e.jsx(h,{size:"sm",onClick:H,disabled:u,children:u?"Loading...":"Refresh"})]}),N&&e.jsx("div",{className:"rounded-md bg-destructive/10 text-destructive p-3 text-sm",children:N}),e.jsxs("div",{className:"flex gap-4 text-sm",children:[e.jsxs("div",{className:"rounded-lg bg-card p-3 flex-1 text-center",children:[e.jsx("div",{className:"text-2xl font-bold",children:c.length}),e.jsx("div",{className:"text-muted-foreground",children:"Users"})]}),e.jsxs("div",{className:"rounded-lg bg-card p-3 flex-1 text-center",children:[e.jsx("div",{className:"text-2xl font-bold",children:te}),e.jsx("div",{className:"text-muted-foreground",children:"Blobs"})]}),e.jsxs("div",{className:"rounded-lg bg-card p-3 flex-1 text-center",children:[e.jsx("div",{className:"text-2xl font-bold",children:S(ee)}),e.jsx("div",{className:"text-muted-foreground",children:"Total"})]})]}),u?e.jsx("div",{className:"text-center py-8 text-muted-foreground",children:"Loading user statistics..."}):c.length===0?e.jsx("div",{className:"text-center py-8 text-muted-foreground",children:"No users have uploaded files yet."}):e.jsx("div",{className:"space-y-1",children:c.map(t=>{var r,n;return e.jsxs("button",{className:"w-full flex items-center gap-3 rounded-md bg-card px-3 py-2.5 text-left hover:bg-accent transition-colors",onClick:()=>T(t),children:[e.jsx("div",{className:"w-9 h-9 rounded-full overflow-hidden bg-muted shrink-0 flex items-center justify-center",children:(r=t.profile)!=null&&r.picture?e.jsx("img",{src:t.profile.picture,alt:"",className:"w-full h-full object-cover"}):e.jsx("span",{className:"text-xs text-muted-foreground",children:"?"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"font-medium truncate",children:((n=t.profile)==null?void 0:n.name)||R(_(t.pubkey))}),e.jsx("div",{className:"text-xs text-muted-foreground font-mono truncate",children:R(_(t.pubkey))})]}),e.jsxs("div",{className:"text-right shrink-0",children:[e.jsxs("div",{className:"text-sm font-medium",children:[t.blob_count," files"]}),e.jsx("div",{className:"text-xs text-muted-foreground",children:S(t.total_size_bytes)})]})]},t.pubkey)})})]}):e.jsx("div",{className:"p-4 text-muted-foreground text-center",children:"Admin access required."})}export{he as default};
   2