claude/heroku-clone-mvp-plan-NREhc #1
2 changed files with 66 additions and 11 deletions
|
|
@ -297,6 +297,18 @@ pub async fn app_detail(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (git_token_status, git_token_clear_btn) = if app.git_token.is_some() {
|
||||||
|
(
|
||||||
|
r#"<span class="badge badge-success">Token configured</span>"#.to_string(),
|
||||||
|
r#"<button class="danger" onclick="clearGitToken()">Clear</button>"#.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
r#"<span class="badge badge-unknown">No token — public repos only</span>"#.to_string(),
|
||||||
|
String::new(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let body = APP_DETAIL_TMPL
|
let body = APP_DETAIL_TMPL
|
||||||
.replace("{{name}}", &app.name)
|
.replace("{{name}}", &app.name)
|
||||||
.replace("{{repo}}", &app.repo_url)
|
.replace("{{repo}}", &app.repo_url)
|
||||||
|
|
@ -308,7 +320,9 @@ pub async fn app_detail(
|
||||||
.replace("{{deploy_rows}}", &deploy_rows)
|
.replace("{{deploy_rows}}", &deploy_rows)
|
||||||
.replace("{{env_rows}}", &env_rows)
|
.replace("{{env_rows}}", &env_rows)
|
||||||
.replace("{{c_badge}}", &container_badge(&container_state))
|
.replace("{{c_badge}}", &container_badge(&container_state))
|
||||||
.replace("{{db_card}}", &db_card);
|
.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()
|
Html(page(&app.name, &body)).into_response()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,26 @@
|
||||||
|
|
||||||
{{db_card}}
|
{{db_card}}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Git Authentication</h2>
|
||||||
|
<p class="muted" style="margin-bottom:12px;font-size:0.9rem">
|
||||||
|
Required for private repos. Store a Personal Access Token (GitHub: <em>repo</em> scope,
|
||||||
|
GitLab: <em>read_repository</em>) so deploys can clone without interactive prompts.
|
||||||
|
Only HTTPS repo URLs are supported; SSH URLs use the server's own key pair.
|
||||||
|
</p>
|
||||||
|
<p style="margin-bottom:12px">{{git_token_status}}</p>
|
||||||
|
<div class="row">
|
||||||
|
<div style="flex:1">
|
||||||
|
<label>Personal Access Token</label>
|
||||||
|
<input id="git-token-input" type="password" placeholder="ghp_…">
|
||||||
|
</div>
|
||||||
|
<div style="align-self:flex-end;display:flex;gap:8px">
|
||||||
|
<button class="primary" onclick="saveGitToken()">Save</button>
|
||||||
|
{{git_token_clear_btn}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Environment Variables</h2>
|
<h2>Environment Variables</h2>
|
||||||
<div class="row" style="margin-bottom:16px">
|
<div class="row" style="margin-bottom:16px">
|
||||||
|
|
@ -145,6 +165,27 @@ async function deprovisionDb() {
|
||||||
if (r.ok) window.location.reload();
|
if (r.ok) window.location.reload();
|
||||||
else alert('Error: ' + await r.text());
|
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() {
|
async function stopApp() {
|
||||||
if (!confirm('Stop ' + APP_ID + '?')) return;
|
if (!confirm('Stop ' + APP_ID + '?')) return;
|
||||||
const r = await fetch('/api/apps/' + APP_ID + '/stop', {method:'POST'});
|
const r = await fetch('/api/apps/' + APP_ID + '/stop', {method:'POST'});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue