fix auth registration and access management ui

This commit is contained in:
lix-2026
2026-05-22 01:47:40 +08:00
parent fdb20300e9
commit 531e845600
16 changed files with 1316 additions and 410 deletions
+256 -130
View File
@@ -8,6 +8,7 @@ pub fn AdminAccessPolicyPage(
#[prop(optional)] workspace_name: Option<String>,
#[prop(optional)] policy_path: Option<String>,
#[prop(optional)] share_grants_path: Option<String>,
#[prop(optional, default = true)] is_admin: bool,
) -> impl IntoView {
let workspace_name = workspace_name
.unwrap_or_else(|| "开发用户 的空间".to_string())
@@ -17,12 +18,17 @@ pub fn AdminAccessPolicyPage(
.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());
let page_title = "授权管理".to_string();
view! {
<PageLayout current_nav="admin" workspace_name={workspace_name.clone()} topbar_title={"目录授权".to_string()} show_admin_access_policy=true>
<PageLayout current_nav={if is_admin { "admin" } else { "home" }} workspace_name={workspace_name.clone()} topbar_title={page_title.clone()} show_admin_access_policy={is_admin}>
<main class="mnote-admin-policy-page" data-testid="mnote-admin-access-policy-page">
<header class="mnote-admin-policy-header">
<h1>"目录授权"</h1>
<p>"管理员可以查看、验证和管理本地目录授权,普通用户不会看到入口。"</p>
<h1>{page_title.clone()}</h1>
<p>{if is_admin {
"管理员可以管理目录授权和分享授权。"
} else {
"查看与你相关的分享授权。"
}}</p>
</header>
<section class="mnote-admin-policy-summary">
@@ -32,7 +38,7 @@ pub fn AdminAccessPolicyPage(
</div>
<div class="mnote-admin-policy-summary-item">
<span class="mnote-admin-policy-summary-label">"策略文件"</span>
<code data-testid="mnote-admin-policy-path">{policy_path.clone()}</code>
<code data-testid="mnote-admin-policy-path">{if is_admin { policy_path.clone() } else { "仅管理员可见".to_string() }}</code>
</div>
<div class="mnote-admin-policy-summary-item">
<span class="mnote-admin-policy-summary-label">"分享授权文件"</span>
@@ -40,102 +46,149 @@ pub fn AdminAccessPolicyPage(
</div>
</section>
<section class="mnote-admin-policy-panel">
<header class="mnote-admin-policy-panel-header">
<h2>"当前策略"</h2>
<button type="button" data-testid="mnote-admin-policy-refresh" data-admin-action="refresh-policy">"刷新"</button>
</header>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-policy-json">{""}</pre>
<div class="mnote-admin-policy-note" data-testid="mnote-admin-policy-message"></div>
</section>
{if is_admin {
view! {
<section class="mnote-admin-policy-panel" data-admin-section="directory">
<header class="mnote-admin-policy-panel-header">
<div>
<h2>"目录授权"</h2>
<p class="mnote-admin-policy-note">"给指定用户授权可访问的本地目录。"</p>
</div>
<button type="button" data-testid="mnote-admin-policy-refresh" data-admin-action="refresh-policy">"刷新"</button>
</header>
<div class="mnote-admin-policy-table" data-testid="mnote-admin-policy-grants-list">
<div class="mnote-admin-policy-empty">"正在读取目录授权..."</div>
</div>
<details class="mnote-admin-policy-debug">
<summary>"查看策略 JSON"</summary>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-policy-json">{""}</pre>
</details>
<div class="mnote-admin-policy-note" data-testid="mnote-admin-policy-message"></div>
</section>
}.into_any()
} else {
view! {
<section class="mnote-admin-policy-panel" data-admin-section="directory" hidden>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-policy-json">{""}</pre>
<div class="mnote-admin-policy-note" data-testid="mnote-admin-policy-message"></div>
</section>
}.into_any()
}}
<section class="mnote-admin-policy-grid">
<form class="mnote-admin-policy-form" data-admin-form="validate-root">
<header><h2>"验证目录"</h2></header>
<label>
<span>"rootUri"</span>
<input data-testid="mnote-admin-root-uri" name="rootUri" type="text" placeholder="file:///mnt/Data1T/Mnote_data/users/..." />
</label>
<label>
<span>"rootPath"</span>
<input data-testid="mnote-admin-root-path" name="rootPath" type="text" placeholder="/mnt/Data1T/Mnote_data/..." />
</label>
<button type="submit" data-testid="mnote-admin-validate-root-submit">"验证"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-validate-result"></pre>
</form>
{if is_admin {
view! {
<section class="mnote-admin-policy-grid" data-admin-only="true">
<form class="mnote-admin-policy-form" data-admin-form="validate-root">
<header><h2>"验证目录"</h2></header>
<label>
<span>"rootUri"</span>
<input data-testid="mnote-admin-root-uri" name="rootUri" type="text" placeholder="file:///mnt/Data1T/Mnote_data/users/..." />
</label>
<label>
<span>"rootPath"</span>
<input data-testid="mnote-admin-root-path" name="rootPath" type="text" placeholder="/mnt/Data1T/Mnote_data/..." />
</label>
<button type="submit" data-testid="mnote-admin-validate-root-submit">"验证"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-validate-result"></pre>
</form>
<form class="mnote-admin-policy-form" data-admin-form="create-grant">
<header><h2>"新增授权"</h2></header>
<label>
<span>"grantId"</span>
<input data-testid="mnote-admin-grant-id" name="grantId" type="text" placeholder="可留空自动生成" />
</label>
<label>
<span>"userId"</span>
<input data-testid="mnote-admin-grant-user-id" name="userId" type="text" placeholder="user_123" required />
</label>
<label>
<span>"rootUri"</span>
<input data-testid="mnote-admin-grant-root-uri" name="rootUri" type="text" />
</label>
<label>
<span>"rootPath"</span>
<input data-testid="mnote-admin-grant-root-path" name="rootPath" type="text" />
</label>
<label>
<span>"permission"</span>
<select data-testid="mnote-admin-grant-permission" name="permission">
<option value="read">"read"</option>
<option value="write">"write"</option>
</select>
</label>
<label>
<span>"recursive"</span>
<input data-testid="mnote-admin-grant-recursive" name="recursive" type="checkbox" checked=true />
</label>
<label>
<span>"capabilities"</span>
<input data-testid="mnote-admin-grant-capabilities" name="capabilities" type="text" placeholder="ai,share" />
</label>
<button type="submit" data-testid="mnote-admin-create-grant-submit">"创建"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-create-result"></pre>
</form>
<form class="mnote-admin-policy-form" data-admin-form="create-grant">
<header><h2>"新增授权"</h2></header>
<label>
<span>"grantId"</span>
<input data-testid="mnote-admin-grant-id" name="grantId" type="text" placeholder="可留空自动生成" />
</label>
<label>
<span>"userId"</span>
<input data-testid="mnote-admin-grant-user-id" name="userId" type="text" placeholder="user_123" required />
</label>
<label>
<span>"rootUri"</span>
<input data-testid="mnote-admin-grant-root-uri" name="rootUri" type="text" />
</label>
<label>
<span>"rootPath"</span>
<input data-testid="mnote-admin-grant-root-path" name="rootPath" type="text" />
</label>
<label>
<span>"permission"</span>
<select data-testid="mnote-admin-grant-permission" name="permission">
<option value="read">"read"</option>
<option value="write">"write"</option>
</select>
</label>
<label>
<span>"recursive"</span>
<input data-testid="mnote-admin-grant-recursive" name="recursive" type="checkbox" checked=true />
</label>
<label>
<span>"capabilities"</span>
<input data-testid="mnote-admin-grant-capabilities" name="capabilities" type="text" placeholder="ai,share" />
</label>
<button type="submit" data-testid="mnote-admin-create-grant-submit">"创建"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-create-result"></pre>
</form>
<form class="mnote-admin-policy-form" data-admin-form="delete-grant">
<header><h2>"删除授权"</h2></header>
<label>
<span>"grantId"</span>
<input data-testid="mnote-admin-delete-grant-id" name="grantId" type="text" placeholder="grant_xxx" required />
</label>
<button type="submit" data-testid="mnote-admin-delete-grant-submit">"删除"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-delete-result"></pre>
</form>
</section>
<form class="mnote-admin-policy-form" data-admin-form="delete-grant">
<header><h2>"删除授权"</h2></header>
<label>
<span>"grantId"</span>
<input data-testid="mnote-admin-delete-grant-id" name="grantId" type="text" placeholder="grant_xxx" required />
</label>
<button type="submit" data-testid="mnote-admin-delete-grant-submit">"删除"</button>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-delete-result"></pre>
</form>
</section>
}.into_any()
} else {
view! {}.into_any()
}}
<section class="mnote-admin-policy-panel" data-testid="mnote-admin-share-grants-panel">
<header class="mnote-admin-policy-panel-header">
<h2>"分享授权"</h2>
<div>
<h2>"分享管理"</h2>
<p class="mnote-admin-policy-note">{if is_admin {
"查看、创建和撤销分享授权。"
} else {
"查看你创建或接收的分享授权。"
}}</p>
</div>
<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-table" data-testid="mnote-admin-share-grants-list">
<div class="mnote-admin-policy-empty">"正在读取分享授权..."</div>
</div>
<details class="mnote-admin-policy-debug">
<summary>"查看分享授权 JSON"</summary>
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-share-grants-json">{""}</pre>
</details>
<div class="mnote-admin-policy-note" data-testid="mnote-admin-share-grants-message"></div>
</section>
<section class="mnote-admin-policy-grid">
<section class="mnote-admin-policy-grid" data-admin-only={if is_admin { "true" } else { "false" }}>
<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>
{if is_admin {
view! {
<label>
<span>"ownerUserId"</span>
<input data-testid="mnote-admin-share-owner-user-id" name="ownerUserId" type="text" placeholder="owner_123" required />
</label>
}.into_any()
} else {
view! {
<input type="hidden" data-testid="mnote-admin-share-owner-user-id" name="ownerUserId" value="" />
}.into_any()
}}
<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 />
@@ -181,6 +234,9 @@ pub fn AdminAccessPolicyPage(
<pre class="mnote-admin-policy-json" data-testid="mnote-admin-delete-share-grant-result"></pre>
</form>
</section>
<script id="__MNOTE_ACCESS_POLICY_PAGE__" type="application/json">
{format!(r#"{{"isAdmin":{}}}"#, if is_admin { "true" } else { "false" })}
</script>
<script>{ADMIN_POLICY_SCRIPT}</script>
</main>
</PageLayout>
@@ -202,12 +258,66 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
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"]');
var policyGrantsList = root.querySelector('[data-testid="mnote-admin-policy-grants-list"]');
var shareGrantsList = root.querySelector('[data-testid="mnote-admin-share-grants-list"]');
var pageConfig = (function () {
var node = document.getElementById('__MNOTE_ACCESS_POLICY_PAGE__');
try { return JSON.parse(node ? node.textContent || '{}' : '{}'); } catch (_) { return {}; }
})();
var isAdmin = pageConfig.isAdmin === true;
function setText(node, value) {
if (!node) return;
node.textContent = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderBadge(value) {
var text = String(value || '').trim() || 'read';
return '<span class="mnote-admin-policy-badge" data-value="' + escapeHtml(text) + '">' + escapeHtml(text) + '</span>';
}
function renderPolicyGrants(payload) {
if (!policyGrantsList) return;
var grants = payload && payload.policy && Array.isArray(payload.policy.grants) ? payload.policy.grants : [];
if (!grants.length) {
policyGrantsList.innerHTML = '<div class="mnote-admin-policy-empty">暂无目录授权</div>';
return;
}
policyGrantsList.innerHTML = grants.map(function(grant) {
return '<article class="mnote-admin-policy-row">' +
'<div><strong>' + escapeHtml(grant.userId || grant.user_id || '未知用户') + '</strong><span>' + escapeHtml(grant.rootPath || grant.root_path || grant.rootUri || grant.root_uri || '') + '</span></div>' +
'<div>' + renderBadge(grant.permission || grant.access) + '</div>' +
'<div><span>' + escapeHtml((grant.capabilities || []).join(', ') || '无能力标记') + '</span></div>' +
'</article>';
}).join('');
}
function renderShareGrants(payload) {
if (!shareGrantsList) return;
var grants = payload && Array.isArray(payload.grants) ? payload.grants : [];
if (!grants.length) {
shareGrantsList.innerHTML = '<div class="mnote-admin-policy-empty">暂无分享授权</div>';
return;
}
shareGrantsList.innerHTML = grants.map(function(grant) {
var active = grant.active === false ? '已撤销' : '有效';
return '<article class="mnote-admin-policy-row">' +
'<div><strong>' + escapeHtml(grant.shareId || grant.id || '未命名分享') + '</strong><span>' + escapeHtml(grant.rootPath || grant.rootUri || '') + '</span></div>' +
'<div>' + renderBadge(grant.permission) + renderBadge(active) + '</div>' +
'<div><span>所有者 ' + escapeHtml(grant.ownerUserId || '') + '</span><span>接收者 ' + escapeHtml(grant.targetUserId || '') + '</span></div>' +
'</article>';
}).join('');
}
function formValues(form) {
var data = new FormData(form);
var capabilities = String(data.get('capabilities') || '')
@@ -262,14 +372,17 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
}
async function refreshPolicy() {
if (!isAdmin) return;
var payload = await requestJson('/api/admin/access-policy', { method: 'GET', headers: {} });
setText(policyJson, payload);
renderPolicyGrants(payload);
setText(message, '已刷新策略');
}
async function refreshShareGrants() {
var payload = await requestJson('/api/admin/share-grants', { method: 'GET', headers: {} });
var payload = await requestJson(isAdmin ? '/api/admin/share-grants' : '/api/user/share-grants', { method: 'GET', headers: {} });
setText(shareGrantsJson, payload);
renderShareGrants(payload);
setText(shareGrantsMessage, '已刷新分享授权');
}
@@ -283,62 +396,71 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
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);
setText(validateResult, '正在验证...');
requestJson('/api/admin/access-policy/validate-root', {
method: 'POST',
body: JSON.stringify({ rootUri: values.rootUri, rootPath: values.rootPath }),
}).then(function (payload) {
setText(validateResult, payload);
setText(message, '目录验证完成');
}).catch(function (error) {
setText(validateResult, { ok: false, error: error.message || '验证失败' });
setText(message, error.message || '验证失败');
var validateRootForm = root.querySelector('[data-admin-form="validate-root"]');
if (validateRootForm) {
validateRootForm.addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
setText(validateResult, '正在验证...');
requestJson('/api/admin/access-policy/validate-root', {
method: 'POST',
body: JSON.stringify({ rootUri: values.rootUri, rootPath: values.rootPath }),
}).then(function (payload) {
setText(validateResult, payload);
setText(message, '目录验证完成');
}).catch(function (error) {
setText(validateResult, { ok: false, error: error.message || '验证失败' });
setText(message, error.message || '验证失败');
});
});
});
}
root.querySelector('[data-admin-form="create-grant"]').addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
setText(createResult, '正在创建...');
requestJson('/api/admin/access-policy/grants', {
method: 'POST',
body: JSON.stringify(values),
}).then(function (payload) {
setText(createResult, payload);
setText(message, '授权已创建');
return refreshPolicy();
}).catch(function (error) {
setText(createResult, { ok: false, error: error.message || '创建失败' });
setText(message, error.message || '创建失败');
var createGrantForm = root.querySelector('[data-admin-form="create-grant"]');
if (createGrantForm) {
createGrantForm.addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
setText(createResult, '正在创建...');
requestJson('/api/admin/access-policy/grants', {
method: 'POST',
body: JSON.stringify(values),
}).then(function (payload) {
setText(createResult, payload);
setText(message, '授权已创建');
return refreshPolicy();
}).catch(function (error) {
setText(createResult, { ok: false, error: error.message || '创建失败' });
setText(message, error.message || '创建失败');
});
});
});
}
root.querySelector('[data-admin-form="delete-grant"]').addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
var grantId = values.id;
setText(deleteResult, '正在删除...');
requestJson('/api/admin/access-policy/grants/' + encodeURIComponent(grantId), {
method: 'DELETE',
headers: {},
}).then(function (payload) {
setText(deleteResult, payload);
setText(message, '授权已删除');
return refreshPolicy();
}).catch(function (error) {
setText(deleteResult, { ok: false, error: error.message || '删除失败' });
setText(message, error.message || '删除失败');
var deleteGrantForm = root.querySelector('[data-admin-form="delete-grant"]');
if (deleteGrantForm) {
deleteGrantForm.addEventListener('submit', function (event) {
event.preventDefault();
var values = formValues(event.currentTarget);
var grantId = values.id;
setText(deleteResult, '正在删除...');
requestJson('/api/admin/access-policy/grants/' + encodeURIComponent(grantId), {
method: 'DELETE',
headers: {},
}).then(function (payload) {
setText(deleteResult, payload);
setText(message, '授权已删除');
return refreshPolicy();
}).catch(function (error) {
setText(deleteResult, { ok: false, error: error.message || '删除失败' });
setText(message, error.message || '删除失败');
});
});
});
}
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', {
requestJson(isAdmin ? '/api/admin/share-grants' : '/api/user/share-grants', {
method: 'POST',
body: JSON.stringify(values),
}).then(function (payload) {
@@ -356,7 +478,7 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
var data = new FormData(event.currentTarget);
var shareId = String(data.get('deleteShareId') || '').trim();
setText(deleteShareGrantResult, '正在撤销...');
requestJson('/api/admin/share-grants/' + encodeURIComponent(shareId), {
requestJson((isAdmin ? '/api/admin/share-grants/' : '/api/user/share-grants/') + encodeURIComponent(shareId), {
method: 'DELETE',
headers: {},
}).then(function (payload) {
@@ -369,11 +491,15 @@ const ADMIN_POLICY_SCRIPT: &str = r#"
});
});
refreshPolicy().catch(function (error) {
setText(message, error.message || '加载策略失败');
});
if (isAdmin) {
refreshPolicy().catch(function (error) {
setText(message, error.message || '加载策略失败');
renderPolicyGrants(null);
});
}
refreshShareGrants().catch(function (error) {
setText(shareGrantsMessage, error.message || '加载分享授权失败');
renderShareGrants(null);
});
})();
"#;