Show SVN repository auth hints
This commit is contained in:
+10
-5
@@ -152,7 +152,7 @@ function renderRepos() {
|
||||
els.repoList.innerHTML = visibleRepos.map((repo) => `<button class="svn-repo-item${repo.id === selectedRepoId ? " is-active" : ""}" type="button" data-id="${escapeHtml(repo.id)}">
|
||||
<span class="repo-name">${escapeHtml(repo.name)}</span>
|
||||
<span class="repo-url">${escapeHtml(repo.url)}</span>
|
||||
<span class="repo-meta">${escapeHtml(repo.source === "server" ? "服务器仓库" : "手动配置")} · ${escapeHtml(repo.localPath || repo.checkoutPath || repo.id)} · ${repo.hasPassword ? "已配置凭据" : "无密码"}</span>
|
||||
<span class="repo-meta">${escapeHtml(repo.source === "server" ? "服务器仓库" : "手动配置")} · ${escapeHtml(repo.localPath || repo.checkoutPath || repo.id)} · ${repo.hasPassword ? "已配置凭据" : (repo.serverAuthRequired ? "需要凭据" : "无密码")}</span>
|
||||
</button>`).join("") || `<p class="empty-state">${repositories.length ? "没有匹配的仓库。" : "暂无 SVN 仓库,可以先点击“接入旧仓库”。"}</p>`;
|
||||
|
||||
const repo = selectedRepo();
|
||||
@@ -163,7 +163,7 @@ function renderRepos() {
|
||||
els.selectedRepoUrl.textContent = compactPath(repo?.url);
|
||||
els.selectedRepoPath.textContent = compactPath(repo?.localPath || repo?.checkoutPath);
|
||||
els.selectedRepoRevision.textContent = repo?.serverRevision ? `r${repo.serverRevision}` : (repo?.lastAction?.at ? formatDate(repo.lastAction.at) : "--");
|
||||
els.selectedRepoSource.textContent = repo ? (repo.source === "server" ? "服务器旧仓库" : "手动配置") : "--";
|
||||
els.selectedRepoSource.textContent = repo ? (repo.source === "server" ? `服务器旧仓库${repo.serverAuthRequired ? " · 需要 SVN 凭据" : ""}` : "手动配置") : "--";
|
||||
els.deleteRepo.disabled = !repo;
|
||||
}
|
||||
|
||||
@@ -172,13 +172,18 @@ function renderServerRepos() {
|
||||
els.serverSummary.textContent = serverRepositories.length
|
||||
? `发现 ${serverRepositories.length} 个,已接入 ${configuredCount} 个`
|
||||
: "未发现旧仓库";
|
||||
els.serverRepoList.innerHTML = serverRepositories.map((repo) => `<button class="svn-server-repo${repo.configured ? " is-linked" : ""}" type="button" data-id="${escapeHtml(repo.id)}" data-configured-id="${escapeHtml(repo.configuredId || "")}">
|
||||
els.serverRepoList.innerHTML = serverRepositories.map((repo) => {
|
||||
const authText = repo.authRequired
|
||||
? `需要凭据${repo.authUsers?.length ? `:${repo.authUsers.join(", ")}` : ""}`
|
||||
: "允许匿名";
|
||||
return `<button class="svn-server-repo${repo.configured ? " is-linked" : ""}" type="button" data-id="${escapeHtml(repo.id)}" data-configured-id="${escapeHtml(repo.configuredId || "")}">
|
||||
<span>
|
||||
<strong>${escapeHtml(repo.name)}</strong>
|
||||
<small>${escapeHtml(repo.url)}</small>
|
||||
</span>
|
||||
<em>${repo.configured ? "已接入" : "待接入"}${repo.revision ? ` · r${escapeHtml(repo.revision)}` : ""}</em>
|
||||
</button>`).join("") || `<p class="empty-state">没有扫描到服务器 SVN 仓库。</p>`;
|
||||
<em>${repo.configured ? "已接入" : "待接入"}${repo.revision ? ` · r${escapeHtml(repo.revision)}` : ""}<br>${escapeHtml(authText)}</em>
|
||||
</button>`;
|
||||
}).join("") || `<p class="empty-state">没有扫描到服务器 SVN 仓库。</p>`;
|
||||
}
|
||||
|
||||
async function loadRepos() {
|
||||
|
||||
@@ -54,6 +54,11 @@ function limitText(value, maxLength = 4000) {
|
||||
return String(value || "").trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function limitStringArray(value, maxItems = 30, maxLength = 120) {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.map((item) => limitText(item, maxLength)).filter(Boolean).slice(0, maxItems);
|
||||
}
|
||||
|
||||
function isRemoteTarget(value) {
|
||||
return REMOTE_TARGET_PATTERN.test(String(value || "").trim());
|
||||
}
|
||||
@@ -138,6 +143,41 @@ async function readTextIfExists(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseSimpleConfig(text) {
|
||||
const result = {};
|
||||
for (const line of String(text || "").split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";") || trimmed.startsWith("[")) continue;
|
||||
const index = trimmed.indexOf("=");
|
||||
if (index === -1) continue;
|
||||
result[trimmed.slice(0, index).trim()] = trimmed.slice(index + 1).trim();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parsePasswdUsers(text) {
|
||||
const users = [];
|
||||
let inUsers = false;
|
||||
for (const line of String(text || "").split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";")) continue;
|
||||
if (/^\[users\]$/i.test(trimmed)) {
|
||||
inUsers = true;
|
||||
continue;
|
||||
}
|
||||
if (/^\[.+\]$/.test(trimmed)) {
|
||||
inUsers = false;
|
||||
continue;
|
||||
}
|
||||
if (!inUsers) continue;
|
||||
const index = trimmed.indexOf("=");
|
||||
if (index === -1) continue;
|
||||
const username = trimmed.slice(0, index).trim();
|
||||
if (username) users.push(username);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
function addAuthArgs(args, repo) {
|
||||
const next = [...args, "--non-interactive"];
|
||||
if (repo.trustServerCert !== false) {
|
||||
@@ -250,6 +290,9 @@ async function discoveredRepoInfo(options, req, rootPath, repoPath) {
|
||||
const name = relativePath || path.basename(repoPath);
|
||||
const current = await readTextIfExists(path.join(repoPath, "db", "current"));
|
||||
const revision = current.split(/\s+/)[0] || "";
|
||||
const svnserveConfig = parseSimpleConfig(await readTextIfExists(path.join(repoPath, "conf", "svnserve.conf")));
|
||||
const authUsers = parsePasswdUsers(await readTextIfExists(path.join(repoPath, "conf", svnserveConfig["password-db"] || "passwd")));
|
||||
const anonAccess = svnserveConfig["anon-access"] || "";
|
||||
return {
|
||||
id: slugify(name),
|
||||
name,
|
||||
@@ -261,6 +304,11 @@ async function discoveredRepoInfo(options, req, rootPath, repoPath) {
|
||||
uuid: await readTextIfExists(path.join(repoPath, "db", "uuid")),
|
||||
fsType: await readTextIfExists(path.join(repoPath, "db", "fs-type")),
|
||||
format: await readTextIfExists(path.join(repoPath, "format")),
|
||||
anonAccess,
|
||||
authAccess: svnserveConfig["auth-access"] || "",
|
||||
realm: svnserveConfig.realm || "",
|
||||
authRequired: anonAccess.toLowerCase() === "none",
|
||||
authUsers: authUsers.slice(0, 30),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -323,6 +371,11 @@ function repoFromDiscovered(discovered, existing = {}) {
|
||||
serverRelativePath: discovered.relativePath,
|
||||
serverRevision: discovered.revision,
|
||||
serverUuid: discovered.uuid,
|
||||
serverAnonAccess: discovered.anonAccess,
|
||||
serverAuthAccess: discovered.authAccess,
|
||||
serverRealm: discovered.realm,
|
||||
serverAuthRequired: discovered.authRequired,
|
||||
serverAuthUsers: discovered.authUsers,
|
||||
description: existing.description || `从服务器 SVN 服务接入:${discovered.repositoryPath}`,
|
||||
}, existing);
|
||||
}
|
||||
@@ -398,6 +451,11 @@ function normalizeRepoPatch(body, existing = {}) {
|
||||
serverRelativePath: limitText(body.serverRelativePath || existing.serverRelativePath, 1000),
|
||||
serverRevision: limitText(body.serverRevision || existing.serverRevision, 80),
|
||||
serverUuid: limitText(body.serverUuid || existing.serverUuid, 120),
|
||||
serverAnonAccess: limitText(body.serverAnonAccess || existing.serverAnonAccess, 80),
|
||||
serverAuthAccess: limitText(body.serverAuthAccess || existing.serverAuthAccess, 80),
|
||||
serverRealm: limitText(body.serverRealm || existing.serverRealm, 200),
|
||||
serverAuthRequired: parseBoolean(body.serverAuthRequired, existing.serverAuthRequired === true),
|
||||
serverAuthUsers: limitStringArray(body.serverAuthUsers || existing.serverAuthUsers),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: existing.createdAt || new Date().toISOString(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user