MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

核心功能:
-  Categories/Series双视图管理(category_view.rs + import_markdown.rs)
-  FUSE Multi-Volume支持(tree_type参数)
-  SSH/SFTP/SCP/rsync协议完整实现(4042行)
-  NFS/SMB Module Phase 1-3完成
-  Archive Module Phase 1-4完成(2916行)
-  Download Center API完整实现
-  S3兼容API实现(560行)

Git配置修正:
-  删除错误origin(gitea.momentry.ddns.net)
-  删除m5max128(指向机器名)
-  设置origin = m5max128gitea.momentry.ddns.net/admin/markbase
-  设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase

数据清理:
-  删除38个临时SQLite(保留accusys.sqlite、demo.sqlite)
-  删除.bak、test_*.bin、调试脚本等临时文件
-  删除临时目录(build/、download files/、raid_test/等)
-  更新.gitignore排除临时文件

架构优化:
- 52个文件修改,2434行新增,4739行删除
- Workspace成员整合(16个crate)
- 数据库状态:accusys.sqlite保留(主demo测试)

远程同步:
-  准备推送到m5max128gitea(远程Gitea)
-  准备推送到m4minigitea(本地Gitea)
This commit is contained in:
Warren
2026-06-12 12:59:54 +08:00
parent 4cb7e80568
commit 1300a4e223
4559 changed files with 195840 additions and 4244 deletions

View File

@@ -105,6 +105,7 @@ color:#64748b;font-size:18px;cursor:pointer}
<div id=mb-detail><button id=mb-detail-close onclick="closeDetail()"></button><div id=mb-detail-body></div></div>
<div id=mb-tree-panel><div id=mb-tree-body></div></div>
<div id=mb-settings-panel><div id=mb-settings-body></div></div>
<div id=mb-s3-panel style="display:none;position:fixed;top:0;left:0;right:0;bottom:52px;background:#0f172a;z-index:9998;overflow-y:auto;padding:16px 24px"><div id=mb-s3-body></div></div>
<div id=mb-bar style="position:fixed;bottom:0;left:0;right:0;background:#1e293b;border-top:1px solid #334155;display:flex;justify-content:center;align-items:center;gap:5px;padding:5px 10px;z-index:9999;font-size:12px">
<button onclick="fetch('/command',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({cmd:'restart'})})" title="Restart"></button>
@@ -117,6 +118,8 @@ color:#64748b;font-size:18px;cursor:pointer}
<span style=color:#475569;font-size:10px>|</span>
<button onclick="toggleTree()" title="File Tree" style="background:none;border:none;color:#60a5fa;cursor:pointer;font-size:16px">🗂</button>
<span style=color:#475569;font-size:10px>|</span>
<button onclick="toggleS3()" title="S3 Service" style="background:none;border:none;color:#60a5fa;cursor:pointer;font-size:16px">☁️</button>
<span style=color:#475569;font-size:10px>|</span>
<button onclick="toggleSettings()" title="Settings" style="background:none;border:none;color:#60a5fa;cursor:pointer;font-size:16px">⚙️</button>
<span style=color:#475569;font-size:10px>|</span>
<button onclick="var t=this.textContent;this.textContent=t===String.fromCodePoint(0x1F50A)?String.fromCodePoint(0x1F507):String.fromCodePoint(0x1F50A)" id=mbvb title=Voice style=font-size:16px>🔊</button>
@@ -426,37 +429,52 @@ d.forEach(function(x){var o=document.createElement("option");o.value=x.num;o.tex
var _tv=false, _tm="tree", _td=null, _tree_user=null;
function toggleTree(){
var token=localStorage.getItem('tree_token');
var savedUser=localStorage.getItem('tree_user');
var savedMode=localStorage.getItem('display_mode')||'categories';
if(token && savedUser){
// Verify token validity
fetch('/api/v2/auth/verify',{
headers:{'Authorization':'Bearer '+token}
})
.then(function(r){return r.json()})
.then(function(d){
if(d.ok && d.user_id===savedUser){
// Token valid, open tree
_tree_user=savedUser;
_tv=!_tv;
document.getElementById("mb-tree-panel").classList.toggle("active",_tv);
if(_tv)loadTree();
}else{
// Token invalid or user mismatch, clear and show login
if(savedMode==='categories' || savedMode==='series'){
_tm=savedMode;
_tv=!_tv;
if(!_tv){
localStorage.setItem('display_mode','categories');
}
document.getElementById("mb-tree-panel").classList.toggle("active",_tv);
if(_tv)loadTree();
}else{
var token=localStorage.getItem('tree_token');
var savedUser=localStorage.getItem('tree_user');
if(token && savedUser){
fetch('/api/v2/auth/verify',{
headers:{'Authorization':'Bearer '+token}
})
.then(function(r){return r.json()})
.then(function(d){
if(d.ok && d.user_id===savedUser){
_tree_user=savedUser;
_tm=savedMode;
_tv=!_tv;
if(!_tv){
localStorage.setItem('display_mode','categories');
}
document.getElementById("mb-tree-panel").classList.toggle("active",_tv);
if(_tv)loadTree();
}else{
localStorage.removeItem('tree_token');
localStorage.removeItem('tree_user');
localStorage.setItem('display_mode','categories');
showTreeLoginModal();
}
})
.catch(function(e){
localStorage.removeItem('tree_token');
localStorage.removeItem('tree_user');
localStorage.setItem('display_mode','categories');
showTreeLoginModal();
}
})
.catch(function(e){
localStorage.removeItem('tree_token');
localStorage.removeItem('tree_user');
});
}else{
localStorage.setItem('display_mode','categories');
showTreeLoginModal();
});
}else{
// No token, show login
showTreeLoginModal();
}
}
}
@@ -579,9 +597,24 @@ function loadTree(searchQuery){
var token=localStorage.getItem('tree_token');
var user=_tree_user||localStorage.getItem('tree_user')||'demo';
var url="/api/v2/tree/"+user+"?mode="+_tm;
if(searchQuery && searchQuery.trim()){
url="/api/v2/tree/"+user+"/search?q="+encodeURIComponent(searchQuery.trim())+"&mode="+_tm;
var url;
if(_tm==="categories"){
if(searchQuery && searchQuery.trim()){
url="/api/v2/files/search?q="+encodeURIComponent(searchQuery.trim())+"&view=category";
}else{
url="/api/v2/categories";
}
}else if(_tm==="series"){
if(searchQuery && searchQuery.trim()){
url="/api/v2/files/search?q="+encodeURIComponent(searchQuery.trim())+"&view=series";
}else{
url="/api/v2/series";
}
}else{
url="/api/v2/tree/"+user+"?mode="+_tm;
if(searchQuery && searchQuery.trim()){
url="/api/v2/tree/"+user+"/search?q="+encodeURIComponent(searchQuery.trim())+"&mode="+_tm;
}
}
fetch(url,{
@@ -597,7 +630,7 @@ function loadTree(searchQuery){
h+="</div>";
// Mode buttons
var modes=[{k:"tree",i:"🌳",l:"Tree"},{k:"list",i:"📋",l:"List"},{k:"grid_sm",i:"🟦",l:"Icons"},{k:"grid_lg",i:"🔲",l:"Large"}];
var modes=[{k:"tree",i:"🌳",l:"All Files"},{k:"categories",i:"📁",l:"Categories"},{k:"series",i:"📦",l:"Series"},{k:"grid_sm",i:"🟦",l:"Icons"},{k:"grid_lg",i:"🔲",l:"Large"}];
h+="<div class=mb-mode-bar>";
modes.forEach(function(m){
h+="<button class=mb-mode-btn"+(_tm==m.k?" active":"")+" onclick='changeMode(\""+m.k+"\")'>";
@@ -605,28 +638,41 @@ function loadTree(searchQuery){
});
h+="<span style=flex:1></span>";
h+="<button class=mb-lock-btn id=mb-lock-icon onclick=toggleLock() title='Toggle edit lock'>"+(_locked?"🔒":"🔓")+"</button>";
if(!_locked){
if(!_locked && _tm==="tree"){
h+="<button class=mb-new-folder-btn onclick='document.getElementById(\"mb-file-input\").click()' style='background:#064e3b;border-color:#4ade80;color:#4ade80'>📤 Upload</button>";
h+="<input type=file id=mb-file-input style=display:none onchange=uploadFile(this)>";
}
if(!_locked){
if(!_locked && _tm==="tree"){
h+="<button class=mb-new-folder-btn onclick=organizeTree() style='background:#0c4a6e;border-color:#38bdf8;color:#38bdf8'>⚡ Agent</button>";
}
h+="<button class=mb-new-folder-btn onclick=newFolder()>+ Folder</button>";
if(!_locked){
if(_tm==="tree"){
h+="<button class=mb-new-folder-btn onclick=newFolder()>+ Folder</button>";
}
if(!_locked && _tm==="tree"){
h+="<button class=mb-new-folder-btn onclick=restoreTree() style='background:#1e3a5f;border-color:#3b82f6;color:#93c5fd'>↻ Restore</button>";
h+="<button class=mb-new-folder-btn onclick=findDupes() style='background:#451a03;color:#fbbf24;border-color:#b45309'>🔍 Dupes</button>";
h+="<button class=mb-new-folder-btn onclick=deleteAll() style='background:#451a03;color:#fbbf24;border-color:#b45309'>✕ All</button>";
h+="<button class=mb-new-folder-btn onclick=logoutTree() style='background:#7f1d1d;color:#fca5a5;border-color:#dc2626'>🚪 Logout</button>";
}
h+="<span style=color:#64748b;font-size:12px;align-self:center>"+d.nodes.length+" nodes</span></div>";
var nodeCount=0;
if(_tm==="categories" && d.categories){
nodeCount=d.total_categories||d.categories.length||0;
}else if(_tm==="series" && d.series){
nodeCount=d.total_series||d.series.length||0;
}else if(d.nodes){
nodeCount=d.nodes.length||0;
}
h+="<span style=color:#64748b;font-size:12px;align-self:center>"+nodeCount+(_tm==="categories"||_tm==="series"?" items":" nodes")+"</span></div>";
if(_tm=="tree")h+=renderTree(d);
if(_tm==="categories")h+=renderCategories(d);
else if(_tm==="series")h+=renderSeries(d);
else if(_tm=="tree")h+=renderTree(d);
else if(_tm=="list")h+=renderList(d);
else h+=renderGrid(d,_tm);
b.innerHTML=h;
}).catch(function(e){
b.innerHTML="<div style=padding:20px;color:#ef4444>Failed to load tree: "+e+"</div>";
b.innerHTML="<div style=padding:20px;color:#ef4444>Failed to load: "+e+"</div>";
});
}
@@ -645,9 +691,14 @@ function clearSearch(){
function changeMode(m){
_tm=m;localStorage.setItem("display_mode",m);
var searchInput=document.getElementById('mb-search-input');
var q=searchInput?searchInput.value:'';
loadTree(q);
if(m==="categories" || m==="series"){
loadTree();
}else{
var searchInput=document.getElementById('mb-search-input');
var q=searchInput?searchInput.value:'';
loadTree(q);
}
}
function dname(n){
@@ -740,6 +791,114 @@ function renderGrid(d,mode){
h+="</div>";return h;
}
function renderCategories(d){
var h="<div style='padding:20px'>";
h+="<h2 style='border:none;margin-bottom:20px;color:#60a5fa'>📁 Categories ("+(d.total_categories||0)+") - "+(d.total_files||0)+" files</h2>";
h+="<div style='display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px'>";
if(d.categories && d.categories.length){
d.categories.forEach(function(cat){
h+="<div onclick='loadCategoryDetail(\""+cat.name+"\")' style='background:#1e293b;border:1px solid #334155;border-radius:8px;padding:16px;cursor:pointer;transition:all 0.2s' onmouseover='this.style.borderColor=\"#3b82f6\";this.style.background=\"#1e3a5f\"' onmouseout='this.style.borderColor=\"#334155\";this.style.background=\"#1e293b\"'>";
h+="<div style='font-size:18px;font-weight:600;color:#e2e8f0;margin-bottom:8px'>📁 "+(cat.display_name||cat.name)+"</div>";
h+="<div style='font-size:13px;color:#94a3b8'>"+(cat.file_count||0)+" files</div>";
if(cat.description){
h+="<div style='font-size:12px;color:#64748b;margin-top:8px'>"+cat.description+"</div>";
}
h+="</div>";
});
}else{
h+="<div style='color:#64748b'>No categories found</div>";
}
h+="</div></div>";
return h;
}
function renderSeries(d){
var h="<div style='padding:20px'>";
h+="<h2 style='border:none;margin-bottom:20px;color:#60a5fa'>📦 Product Series ("+(d.total_series||0)+") - "+(d.total_files||0)+" files</h2>";
h+="<div style='display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px'>";
if(d.series && d.series.length){
d.series.forEach(function(ser){
h+="<div onclick='loadSeriesDetail(\""+ser.name+"\")' style='background:#1e293b;border:1px solid #334155;border-radius:8px;padding:16px;cursor:pointer;transition:all 0.2s' onmouseover='this.style.borderColor=\"#3b82f6\";this.style.background=\"#1e3a5f\"' onmouseout='this.style.borderColor=\"#334155\";this.style.background=\"#1e293b\"'>";
h+="<div style='font-size:18px;font-weight:600;color:#e2e8f0;margin-bottom:8px'>📦 "+(ser.display_name||ser.name)+"</div>";
h+="<div style='font-size:13px;color:#94a3b8'>"+(ser.file_count||0)+" files</div>";
if(ser.description){
h+="<div style='font-size:12px;color:#64748b;margin-top:8px'>"+ser.description+"</div>";
}
h+="</div>";
});
}else{
h+="<div style='color:#64748b'>No series found</div>";
}
h+="</div></div>";
return h;
}
function loadCategoryDetail(name){
var b=document.getElementById("mb-tree-body");
if(!b)return;
b.innerHTML="<div style=text-align:center;padding:40px;color:#64748b>Loading category...</div>";
fetch("/api/v2/categories/"+encodeURIComponent(name)).then(function(r){return r.json()}).then(function(d){
var h="<div style='padding:20px'>";
h+="<button onclick='loadTree()' style='background:#1e293b;border:1px solid #334155;color:#94a3b8;padding:8px 16px;border-radius:6px;cursor:pointer;margin-bottom:16px'>← Back to Categories</button>";
h+="<h2 style='border:none;margin:20px 0;color:#60a5fa'>📁 "+(d.category.display_name||d.category.name)+" - "+d.category.file_count+" files</h2>";
if(d.series_groups && d.series_groups.length){
d.series_groups.forEach(function(group){
h+="<h3 style='color:#94a3b8;margin-top:24px;margin-bottom:12px'>📦 "+group.series_name+"</h3>";
h+="<table style='width:100%;border-collapse:collapse'>";
h+="<thead><tr style='border-bottom:2px solid #334155'><th style='text-align:left;padding:8px;color:#94a3b8'>File</th><th style='text-align:right;padding:8px;color:#94a3b8;width:100px'>Size</th><th style='text-align:center;padding:8px;color:#94a3b8;width:80px'>Download</th></tr></thead>";
h+="<tbody>";
group.files.forEach(function(file){
h+="<tr style='border-bottom:1px solid #1e293b'>";
h+="<td style='padding:8px;color:#e2e8f0'>"+file.filename+"</td>";
h+="<td style='padding:8px;color:#94a3b8;text-align:right'>"+file.size+"</td>";
h+="<td style='padding:8px;text-align:center'><a href='"+file.download_url+"' target='_blank' style='color:#3b82f6;text-decoration:none'>⬇️</a></td>";
h+="</tr>";
});
h+="</tbody></table>";
});
}
h+="</div>";
b.innerHTML=h;
}).catch(function(e){
b.innerHTML="<div style=padding:20px;color:#ef4444>Failed to load category: "+e+"</div>";
});
}
function loadSeriesDetail(name){
var b=document.getElementById("mb-tree-body");
if(!b)return;
b.innerHTML="<div style=text-align:center;padding:40px;color:#64748b>Loading series...</div>";
fetch("/api/v2/series/"+encodeURIComponent(name)).then(function(r){return r.json()}).then(function(d){
var h="<div style='padding:20px'>";
h+="<button onclick='loadTree()' style='background:#1e293b;border:1px solid #334155;color:#94a3b8;padding:8px 16px;border-radius:6px;cursor:pointer;margin-bottom:16px'>← Back to Series</button>";
h+="<h2 style='border:none;margin:20px 0;color:#60a5fa'>📦 "+(d.series.display_name||d.series.name)+" - "+d.series.file_count+" files</h2>";
if(d.categories && d.categories.length){
d.categories.forEach(function(cat){
h+="<h3 style='color:#94a3b8;margin-top:24px;margin-bottom:12px'>📁 "+cat.category_name+"</h3>";
h+="<table style='width:100%;border-collapse:collapse'>";
h+="<thead><tr style='border-bottom:2px solid #334155'><th style='text-align:left;padding:8px;color:#94a3b8'>File</th><th style='text-align:right;padding:8px;color:#94a3b8;width:100px'>Size</th><th style='text-align:center;padding:8px;color:#94a3b8;width:80px'>Download</th></tr></thead>";
h+="<tbody>";
cat.files.forEach(function(file){
h+="<tr style='border-bottom:1px solid #1e293b'>";
h+="<td style='padding:8px;color:#e2e8f0'>"+file.filename+"</td>";
h+="<td style='padding:8px;color:#94a3b8;text-align:right'>"+file.size+"</td>";
h+="<td style='padding:8px;text-align:center'><a href='"+file.download_url+"' target='_blank' style='color:#3b82f6;text-decoration:none'>⬇️</a></td>";
h+="</tr>";
});
h+="</tbody></table>";
});
}
h+="</div>";
b.innerHTML=h;
}).catch(function(e){
b.innerHTML="<div style=padding:20px;color:#ef4444>Failed to load series: "+e+"</div>";
});
}
// DETAIL PANEL
function showDetail(fuuid){
if(!fuuid)return;
@@ -1138,6 +1297,118 @@ function toggleLock(){
loadTree();
}
// ═══════════════ S3 PANEL ═══════════════
var _s3v=false;
function toggleS3(){
_s3v=!_s3v;
document.getElementById("mb-s3-panel").classList.toggle("active",_s3v);
if(_s3v)loadS3Panel();
}
function loadS3Panel(){
var b=document.getElementById("mb-s3-body");
if(!b)return;
b.innerHTML="<div style=text-align:center;padding:40px;color:#64748b>Loading...</div>";
fetch("/api/v2/s3/status").then(function(r){return r.json()}).then(function(d){
var h="<div class=mb-mode-bar>";
h+="<span style=color:#60a5fa;font-size:16px>☁️</span>";
h+="<span style=font-size:14px;color:#e2e8f0;margin-left:8px>S3 Service</span>";
h+="<span style=flex:1></span>";
h+="<button onclick=toggleS3() style='background:none;border:none;color:#64748b;font-size:18px;cursor:pointer'>✕</button>";
h+="</div>";
// S3 Status Section
h+="<div class=mb-config-section>";
h+="<div class=mb-config-header>S3 STATUS</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>Status</span>";
h+="<span class=mb-config-value>"+(d.enabled?"✅ Running":"❌ Disabled")+"</span>";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>Endpoint</span>";
h+="<span class=mb-config-value>"+d.endpoint+"</span>";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>Region</span>";
h+="<span class=mb-config-value>"+d.region+"</span>";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>Buckets</span>";
h+="<span class=mb-config-value>"+d.buckets_count+"</span>";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>Access Keys</span>";
h+="<span class=mb-config-value>"+d.keys_count+"</span>";
h+="</div>";
h+="</div>";
// Access Keys Section
h+="<div class=mb-config-section>";
h+="<div class=mb-config-header>S3 ACCESS KEYS</div>";
h+="<div style=padding:8px;color:#94a3b8;font-size:12px>";
h+="AWS Signature V4 authentication required";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>markbase_access_key_001</span>";
h+="<span class=mb-config-value>Access: warren</span>";
h+="<button class=mb-config-edit-btn onclick=copyS3Key('markbase_access_key_001')>Copy</button>";
h+="</div>";
h+="<div class=mb-config-item>";
h+="<span class=mb-config-label>markbase_access_key_002</span>";
h+="<span class=mb-config-value>Access: demo</span>";
h+="<button class=mb-config-edit-btn onclick=copyS3Key('markbase_access_key_002')>Copy</button>";
h+="</div>";
h+="<button class=mb-new-folder-btn onclick=generateS3Key() style=margin-top:8px>Generate New Key</button>";
h+="</div>";
// Client Usage Section
h+="<div class=mb-config-section>";
h+="<div class=mb-config-header>CLIENT USAGE (Python boto3)</div>";
h+="<pre style='background:#0f172a;padding:12px;border-radius:8px;font-size:12px;color:#4ade80;white-space:pre-wrap'>";
h+="import boto3\n\n";
h+="s3 = boto3.client(\n";
h+=" 's3',\n";
h+=" endpoint_url='"+d.endpoint+"',\n";
h+=" aws_access_key_id='markbase_access_key_001',\n";
h+=" aws_secret_access_key='markbase_secret_key_xyz123'\n";
h+=")\n\n";
h+="# List buckets\n";
h+="buckets = s3.list_buckets()\n";
h+="for b in buckets['Buckets']:\n";
h+=" print(b['Name'])\n\n";
h+="# List objects\n";
h+="objects = s3.list_objects_v2(Bucket='warren')\n";
h+="for obj in objects['Contents']:\n";
h+=" print(obj['Key'])\n";
h+="</pre>";
h+="</div>";
b.innerHTML=h;
}).catch(function(e){
b.innerHTML="<div style=padding:20px;color:#ef4444>Failed to load S3 status: "+e+"</div>";
});
}
function copyS3Key(accessKey){
navigator.clipboard.writeText(accessKey);
toast("Copied: "+accessKey);
}
function generateS3Key(){
fetch("/api/v2/s3/generate-key",{method:"POST"})
.then(function(r){return r.json()})
.then(function(d){
if(d.access_key){
toast("Generated: "+d.access_key);
loadS3Panel();
}else{
toast("Error: "+(d.error||"unknown"));
}
});
}
// Init
(function(){
var s=localStorage.getItem("display_mode");