feat: support private repo cloning with HTTP basic auth
Deploy PR-Helper / deploy (push) Successful in 30s

- Add SSH URL detection with friendly prompt to use HTTPS instead
- Add private repo toggle (default off) with username/password fields
- Persist auth_type and credential in repositories table
- Pass stored credentials to PullRepo for authenticated pulls
- Incremental migration adds auth_type and credential columns
This commit is contained in:
2026-06-21 14:58:12 +08:00
parent 82666cff26
commit a6bd46db34
5 changed files with 108 additions and 21 deletions
+50 -8
View File
@@ -14,13 +14,31 @@
<!-- Clone Form -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 class="text-lg font-semibold mb-4">克隆仓库</h2>
<form id="clone-form" class="flex gap-3">
<input type="text" id="repo-url" name="url" placeholder="https://github.com/user/repo.git"
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" required>
<button type="submit" id="clone-btn"
class="bg-indigo-600 text-white px-5 py-2 rounded-md text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
克隆
</button>
<form id="clone-form" class="space-y-3">
<div class="flex gap-3">
<input type="text" id="repo-url" name="url" placeholder="https://github.com/user/repo.git"
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" required>
<button type="submit" id="clone-btn"
class="bg-indigo-600 text-white px-5 py-2 rounded-md text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
克隆
</button>
</div>
<div id="ssh-warning" class="hidden rounded-md bg-yellow-50 border border-yellow-200 px-4 py-3 text-sm text-yellow-800">
检测到 SSH 格式地址,请使用 HTTPS 格式。例如:<code class="bg-yellow-100 px-1 rounded">https://github.com/user/repo.git</code>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="private-toggle" class="sr-only peer">
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div>
</label>
<span class="text-sm text-gray-600">私有仓库</span>
</div>
<div id="auth-fields" class="hidden grid grid-cols-2 gap-3">
<input type="text" id="repo-username" placeholder="用户名(可选)"
class="rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
<input type="password" id="repo-password" placeholder="密码或 Personal Access Token"
class="rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
</form>
<div id="clone-progress" class="hidden mt-4">
<div class="flex items-center gap-3 mb-2">
@@ -74,10 +92,30 @@
{{template "footer" .}}
</div>
<script>
// Toggle private repo auth fields
document.getElementById('private-toggle').addEventListener('change', function() {
document.getElementById('auth-fields').classList.toggle('hidden', !this.checked);
});
// Detect SSH URLs on input
document.getElementById('repo-url').addEventListener('input', function() {
const val = this.value.trim();
const isSSH = /^(git@|ssh:\/\/)/.test(val);
document.getElementById('ssh-warning').classList.toggle('hidden', !isSSH);
});
document.getElementById('clone-form').addEventListener('submit', async function(e) {
e.preventDefault();
const url = document.getElementById('repo-url').value.trim();
if (!url) return;
// Block SSH URLs
if (/^(git@|ssh:\/\/)/.test(url)) {
const sshWarn = document.getElementById('ssh-warning');
sshWarn.classList.remove('hidden');
return;
}
const btn = document.getElementById('clone-btn');
const progress = document.getElementById('clone-progress');
const status = document.getElementById('clone-status');
@@ -90,12 +128,16 @@ document.getElementById('clone-form').addEventListener('submit', async function(
bar.style.width = '0%';
status.textContent = '正在连接...';
const isPrivate = document.getElementById('private-toggle').checked;
const username = isPrivate ? document.getElementById('repo-username').value.trim() : '';
const password = isPrivate ? document.getElementById('repo-password').value : '';
try {
const resp = await fetch('/api/repos', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
credentials: 'same-origin',
body: JSON.stringify({url: url})
body: JSON.stringify({url, username, password})
});
if (!resp.ok) {