完善共享授权与本地同步闭环

实现独立 share-grants 控制面,区分产品分享授权与本机目录授权。

收紧 shared AI session 的资源范围,并补充 shared cache、pending change 与冲突报告 sidecar。

更新当前架构与执行 checklist,记录 P4 已完成项和剩余 browser smoke。
This commit is contained in:
lix-2026
2026-05-19 11:11:08 +08:00
parent fc4a47e597
commit a1b28a8e38
7 changed files with 1889 additions and 48 deletions
@@ -7,6 +7,7 @@ use leptos::prelude::*;
pub fn AdminAccessPolicyPage(
#[prop(optional)] workspace_name: Option<String>,
#[prop(optional)] policy_path: Option<String>,
#[prop(optional)] share_grants_path: Option<String>,
) -> impl IntoView {
let workspace_name = workspace_name
.unwrap_or_else(|| "开发用户 的空间".to_string())
@@ -14,6 +15,8 @@ pub fn AdminAccessPolicyPage(
.to_string();
let policy_path = policy_path
.unwrap_or_else(|| "/mnt/Data1T/Mnote_data/control-plane/access-policy.json".to_string());
let share_grants_path = share_grants_path
.unwrap_or_else(|| "/mnt/Data1T/Mnote_data/control-plane/share-grants.json".to_string());
view! {
<PageLayout current_nav="admin" workspace_name={workspace_name.clone()} topbar_title={"目录授权".to_string()} show_admin_access_policy=true>
<main class="mnote-admin-policy-page" data-testid="mnote-admin-access-policy-page">
@@ -31,6 +34,10 @@ pub fn AdminAccessPolicyPage(
<span class="mnote-admin-policy-summary-label">"策略文件"</span>
<code data-testid="mnote-admin-policy-path">{policy_path.clone()}</code>
</div>
<div class="mnote-admin-policy-summary-item">
<span class="mnote-admin-policy-summary-label">"分享授权文件"</span>
<code data-testid="mnote-admin-share-grants-path">{share_grants_path.clone()}</code>
</div>
</section>
<section class="mnote-admin-policy-panel">
@@ -104,6 +111,76 @@ pub fn AdminAccessPolicyPage(
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-delete-result"></pre>
</form>
</section>
<section class="mnote-admin-policy-panel" data-testid="mnote-admin-share-grants-panel">
<header class="mnote-admin-policy-panel-header">
<h2>"分享授权"</h2>
<button type="button" data-testid="mnote-admin-share-grants-refresh" data-admin-action="refresh-share-grants">"刷新"</button>
</header>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-share-grants-json">{""}</pre>
<div class="mnote-admin-policy-note" data-testid="mnote-admin-share-grants-message"></div>
</section>
<section class="mnote-admin-policy-grid">
<form class="mnote-admin-policy-form" data-admin-form="create-share-grant">
<header><h2>"新增分享授权"</h2></header>
<label>
<span>"grantId"</span>
<input data-testid="mnote-admin-share-grant-id" name="shareGrantId" type="text" placeholder="可留空自动生成" />
</label>
<label>
<span>"shareId"</span>
<input data-testid="mnote-admin-share-id" name="shareId" type="text" placeholder="share_xxx" required />
</label>
<label>
<span>"ownerUserId"</span>
<input data-testid="mnote-admin-share-owner-user-id" name="ownerUserId" type="text" placeholder="owner_123" required />
</label>
<label>
<span>"targetUserId"</span>
<input data-testid="mnote-admin-share-target-user-id" name="targetUserId" type="text" placeholder="target_123" required />
</label>
<label>
<span>"rootUri"</span>
<input data-testid="mnote-admin-share-root-uri" name="shareRootUri" type="text" />
</label>
<label>
<span>"rootPath"</span>
<input data-testid="mnote-admin-share-root-path" name="shareRootPath" type="text" />
</label>
<label>
<span>"documentId"</span>
<input data-testid="mnote-admin-share-document-id" name="documentId" type="text" placeholder="doc_123" />
</label>
<label>
<span>"allowedResourceIds"</span>
<input data-testid="mnote-admin-share-resource-ids" name="allowedResourceIds" type="text" placeholder="doc_123,mindmap_456" />
</label>
<label>
<span>"permission"</span>
<select data-testid="mnote-admin-share-permission" name="sharePermission">
<option value="read">"read"</option>
<option value="write">"write"</option>
</select>
</label>
<label>
<span>"capabilities"</span>
<input data-testid="mnote-admin-share-capabilities" name="shareCapabilities" type="text" placeholder="ai,share" />
</label>
<button type="submit" data-testid="mnote-admin-create-share-grant-submit">"创建分享授权"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-create-share-grant-result"></pre>
</form>
<form class="mnote-admin-policy-form" data-admin-form="delete-share-grant">
<header><h2>"撤销分享授权"</h2></header>
<label>
<span>"shareId 或 grantId"</span>
<input data-testid="mnote-admin-delete-share-id" name="deleteShareId" type="text" placeholder="share_xxx" required />
</label>
<button type="submit" data-testid="mnote-admin-delete-share-grant-submit">"撤销"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-delete-share-grant-result"></pre>
</form>
</section>
<script>{ADMIN_POLICY_SCRIPT}</script>
</main>
</PageLayout>
@@ -116,10 +193,15 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
if (!root) return;
var message = root.querySelector('[data-testid="mnote-admin-policy-message"]');
var policyJson = root.querySelector('[data-testid="mnote-admin-policy-json"]');
var shareGrantsJson = root.querySelector('[data-testid="mnote-admin-share-grants-json"]');
var validateResult = root.querySelector('[data-testid="mnote-admin-validate-result"]');
var createResult = root.querySelector('[data-testid="mnote-admin-create-result"]');
var deleteResult = root.querySelector('[data-testid="mnote-admin-delete-result"]');
var shareGrantsMessage = root.querySelector('[data-testid="mnote-admin-share-grants-message"]');
var createShareGrantResult = root.querySelector('[data-testid="mnote-admin-create-share-grant-result"]');
var deleteShareGrantResult = root.querySelector('[data-testid="mnote-admin-delete-share-grant-result"]');
var refreshButton = root.querySelector('[data-admin-action="refresh-policy"]');
var refreshShareGrantsButton = root.querySelector('[data-admin-action="refresh-share-grants"]');
function setText(node, value) {
if (!node) return;
@@ -143,6 +225,29 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
};
}
function splitList(value) {
return String(value || '')
.split(/[,\s]+/)
.map(function (item) { return item.trim(); })
.filter(Boolean);
}
function shareGrantFormValues(form) {
var data = new FormData(form);
return {
id: String(data.get('shareGrantId') || '').trim(),
shareId: String(data.get('shareId') || '').trim(),
ownerUserId: String(data.get('ownerUserId') || '').trim(),
targetUserId: String(data.get('targetUserId') || '').trim(),
rootUri: String(data.get('shareRootUri') || '').trim(),
rootPath: String(data.get('shareRootPath') || '').trim(),
documentId: String(data.get('documentId') || '').trim(),
allowedResourceIds: splitList(data.get('allowedResourceIds')),
permission: String(data.get('sharePermission') || 'read').trim(),
capabilities: splitList(data.get('shareCapabilities')),
};
}
async function requestJson(url, options) {
var response = await fetch(url, {
credentials: 'include',
@@ -162,11 +267,22 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
setText(message, '已刷新策略');
}
async function refreshShareGrants() {
var payload = await requestJson('/api/admin/share-grants', { method: 'GET', headers: {} });
setText(shareGrantsJson, payload);
setText(shareGrantsMessage, '已刷新分享授权');
}
refreshButton && refreshButton.addEventListener('click', function () {
setText(message, '正在刷新策略...');
refreshPolicy().catch(function (error) { setText(message, error.message || '刷新失败'); });
});
refreshShareGrantsButton && refreshShareGrantsButton.addEventListener('click', function () {
setText(shareGrantsMessage, '正在刷新分享授权...');
refreshShareGrants().catch(function (error) { setText(shareGrantsMessage, error.message || '刷新失败'); });
});
root.querySelector('[data-admin-form="validate-root"]').addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
@@ -218,9 +334,47 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
});
});
root.querySelector('[data-admin-form="create-share-grant"]').addEventListener('submit', function (event) {
event.preventDefault();
var values = shareGrantFormValues(event.currentTarget);
setText(createShareGrantResult, '正在创建...');
requestJson('/api/admin/share-grants', {
method: 'POST',
body: JSON.stringify(values),
}).then(function (payload) {
setText(createShareGrantResult, payload);
setText(shareGrantsMessage, '分享授权已创建');
return refreshShareGrants();
}).catch(function (error) {
setText(createShareGrantResult, { ok: false, error: error.message || '创建失败' });
setText(shareGrantsMessage, error.message || '创建失败');
});
});
root.querySelector('[data-admin-form="delete-share-grant"]').addEventListener('submit', function (event) {
event.preventDefault();
var data = new FormData(event.currentTarget);
var shareId = String(data.get('deleteShareId') || '').trim();
setText(deleteShareGrantResult, '正在撤销...');
requestJson('/api/admin/share-grants/' + encodeURIComponent(shareId), {
method: 'DELETE',
headers: {},
}).then(function (payload) {
setText(deleteShareGrantResult, payload);
setText(shareGrantsMessage, '分享授权已撤销');
return refreshShareGrants();
}).catch(function (error) {
setText(deleteShareGrantResult, { ok: false, error: error.message || '撤销失败' });
setText(shareGrantsMessage, error.message || '撤销失败');
});
});
refreshPolicy().catch(function (error) {
setText(message, error.message || '加载策略失败');
});
refreshShareGrants().catch(function (error) {
setText(shareGrantsMessage, error.message || '加载分享授权失败');
});
})();
"#;
@@ -239,8 +393,12 @@ mod tests {
});
assert!(html.contains("mnote-admin-access-policy-page"));
assert!(html.contains("mnote-admin-policy-json"));
assert!(html.contains("mnote-admin-share-grants-panel"));
assert!(html.contains("mnote-admin-share-grants-json"));
assert!(html.contains("mnote-admin-validate-root-submit"));
assert!(html.contains("mnote-admin-create-grant-submit"));
assert!(html.contains("mnote-admin-delete-grant-submit"));
assert!(html.contains("mnote-admin-create-share-grant-submit"));
assert!(html.contains("mnote-admin-delete-share-grant-submit"));
}
}