diff --git a/server/src/routes/ui.rs b/server/src/routes/ui.rs index 9e51a6f..cf0f844 100644 --- a/server/src/routes/ui.rs +++ b/server/src/routes/ui.rs @@ -297,18 +297,32 @@ pub async fn app_detail( } }; + let (git_token_status, git_token_clear_btn) = if app.git_token.is_some() { + ( + r#"Token configured"#.to_string(), + r#""#.to_string(), + ) + } else { + ( + r#"No token — public repos only"#.to_string(), + String::new(), + ) + }; + let body = APP_DETAIL_TMPL - .replace("{{name}}", &app.name) - .replace("{{repo}}", &app.repo_url) - .replace("{{branch}}", &app.branch) - .replace("{{port}}", &app.port.to_string()) - .replace("{{host}}", &host) - .replace("{{app_id}}", &app.id) - .replace("{{secret}}", &app.webhook_secret) - .replace("{{deploy_rows}}", &deploy_rows) - .replace("{{env_rows}}", &env_rows) - .replace("{{c_badge}}", &container_badge(&container_state)) - .replace("{{db_card}}", &db_card); + .replace("{{name}}", &app.name) + .replace("{{repo}}", &app.repo_url) + .replace("{{branch}}", &app.branch) + .replace("{{port}}", &app.port.to_string()) + .replace("{{host}}", &host) + .replace("{{app_id}}", &app.id) + .replace("{{secret}}", &app.webhook_secret) + .replace("{{deploy_rows}}", &deploy_rows) + .replace("{{env_rows}}", &env_rows) + .replace("{{c_badge}}", &container_badge(&container_state)) + .replace("{{db_card}}", &db_card) + .replace("{{git_token_status}}", &git_token_status) + .replace("{{git_token_clear_btn}}", &git_token_clear_btn); Html(page(&app.name, &body)).into_response() } diff --git a/server/templates/app_detail.html b/server/templates/app_detail.html index f071f89..af06614 100644 --- a/server/templates/app_detail.html +++ b/server/templates/app_detail.html @@ -35,6 +35,26 @@ {{db_card}} +
+

Git Authentication

+

+ Required for private repos. Store a Personal Access Token (GitHub: repo scope, + GitLab: read_repository) so deploys can clone without interactive prompts. + Only HTTPS repo URLs are supported; SSH URLs use the server's own key pair. +

+

{{git_token_status}}

+
+
+ + +
+
+ + {{git_token_clear_btn}} +
+
+
+

Environment Variables

@@ -145,6 +165,27 @@ async function deprovisionDb() { if (r.ok) window.location.reload(); else alert('Error: ' + await r.text()); } +async function saveGitToken() { + const tok = document.getElementById('git-token-input').value; + if (!tok) { alert('Enter a token first'); return; } + const r = await fetch('/api/apps/' + APP_ID, { + method: 'PATCH', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({git_token: tok}), + }); + if (r.ok) window.location.reload(); + else alert('Error saving token: ' + await r.text()); +} +async function clearGitToken() { + if (!confirm('Remove the stored git token for ' + APP_ID + '?')) return; + const r = await fetch('/api/apps/' + APP_ID, { + method: 'PATCH', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({git_token: ''}), + }); + if (r.ok) window.location.reload(); + else alert('Error clearing token: ' + await r.text()); +} async function stopApp() { if (!confirm('Stop ' + APP_ID + '?')) return; const r = await fetch('/api/apps/' + APP_ID + '/stop', {method:'POST'});