@@ -40,7 +40,7 @@ async function api(url,opt={}){const r=await fetch(url,opt);if(!r.ok){let t="请
function esc(s){return String(s).replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[c]))}
function fmt(n){if(n<1024)return n+" B";if(n<1048576)return (n/1024).toFixed(1)+" KB";if(n<1073741824)return (n/1048576).toFixed(1)+" MB";return (n/1073741824).toFixed(1)+" GB"}
async function init(){try{me=await api("/chfs/me")}catch{me={username:"",home:"",admin:false}};updateUser();current=me.home||"";await refresh()}
-function updateUser(){$("who").textContent=me.username?me.username: "访客";$("loginBtn").hidden=!!me.username;$("logoutBtn").hidden=!me.username;$("usersBtn").hidden=!me.admin}
+function updateUser(){$("who").textContent=me.username?me.username: "访客";$("loginBtn").hidden=!!me.username;$("logoutBtn").hidden=!me.username;$("usersBtn").hidden=!me.admin;$("pwdBtn").hidden=!me.username||!me.managed}
async function refresh(){try{const q=encodeURIComponent(current);const data=await api("/chfs/files?filepath="+q);render(data.files||[]);$("crumb").textContent="/"+current;$("notice").innerHTML=""}catch(e){$("notice").innerHTML='
'+esc(e.message)+"
"}}
function render(items){const body=$("files");body.innerHTML="";if(!items.length){body.innerHTML='
这里还没有文件
';return}for(const f of items){const p=current?current+"/"+f.name:f.name;const row=document.createElement("tr");const icon=f.dir?"📁":"📄";row.innerHTML='
';body.appendChild(row)}}
function openDir(p){current=p;refresh()}function goUp(){if(!current)return;const root=me.home||"";if(current===root)return;let p=current.split("/");p.pop();let n=p.join("/");if(root&&!(n===root||n.startsWith(root+"/")))n=root;current=n;refresh()}
@@ -53,13 +53,16 @@ async function newText(){const n=prompt("文件名","new.txt");if(!n)return;cons
$("upload").addEventListener("change",async e=>{const fd=new FormData();fd.append("folder",current);for(const f of e.target.files)fd.append("file",f);try{await api("/chfs/upload",{method:"POST",body:fd});refresh()}catch(x){alert(x.message)}e.target.value=""})
$("search").addEventListener("keydown",async e=>{if(e.key!=="Enter")return;const s=e.target.value.trim();if(!s){refresh();return}try{const d=await api("/chfs/search?str="+encodeURIComponent(s));renderSearch(d.files||[])}catch(x){alert(x.message)}})
function renderSearch(items){$("crumb").textContent="搜索结果";const body=$("files");body.innerHTML="";for(const f of items){const row=document.createElement("tr");row.innerHTML='
')}
+async function savePassword(){try{await api("/chfs/password",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:$("cp").value,password:$("npw").value})});alert("密码已更新");closeModal()}catch(e){alert(e.message)}}
function openLogin(){show('
登录
')}
async function login(){try{await fetch("/chfs/session",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({user:$("u").value,pwd:$("p").value})}).then(async r=>{if(!r.ok)throw Error((await r.json()).error||"登录失败")});closeModal();await init()}catch(e){alert(e.message)}}
async function logout(){await fetch("/chfs/session",{method:"DELETE"});location.reload()}
function show(html){$("dialog").innerHTML=html;$("modal").classList.add("show")}function closeModal(){$("modal").classList.remove("show")}
$("modal").addEventListener("click",e=>{if(e.target.id==="modal")closeModal()})
-async function showUsers(){try{const d=await api("/chfs/users");let html='