Files

298 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}SSH Setup - Server Monitoring{% endblock %}
{% block page_title %}SSH Setup{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row g-4">
<!-- SSH Key Management -->
<div class="col-lg-6">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-key me-2"></i>SSH Key Pair</h5>
</div>
<div class="card-body">
{% if key_exists %}
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>SSH key exists at
<code>~/.ssh/ansible_key</code>
</div>
{% if public_key %}
<div class="mb-3">
<label class="form-label fw-semibold">Public Key</label>
<textarea class="form-control font-monospace" rows="4" readonly>{{ public_key }}</textarea>
<small class="text-muted">Copy this key to <code>~/.ssh/authorized_keys</code> on each device.</small>
</div>
{% endif %}
{% else %}
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>No SSH key found. Generate one below.
</div>
{% endif %}
<form method="post" action="{{ url_for('ansible_web.generate_ssh_keys') }}">
<button type="submit" class="btn btn-primary">
<i class="fas fa-sync me-1"></i>
{{ 'Regenerate' if key_exists else 'Generate' }} SSH Keys
</button>
</form>
</div>
</div>
</div>
<!-- SSH Settings -->
<div class="col-lg-6">
<div class="card shadow-sm">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0"><i class="fas fa-lock me-2"></i>SSH Authentication Settings</h5>
</div>
<div class="card-body">
<p class="text-muted small">
Configure SSH authentication for Ansible. Enable password mode to authenticate
with a username/password instead of SSH keys.
</p>
<form method="post" action="{{ url_for('ansible_web.save_ssh_settings') }}">
<div class="mb-3">
<label class="form-label fw-semibold">SSH Password</label>
<div class="input-group">
<input type="password" name="ssh_fallback_password" id="sshFallbackPassword"
class="form-control"
value="{{ settings.get('ssh_fallback_password', '') }}"
placeholder="Enter device password"
required>
<button class="btn btn-outline-secondary" type="button"
onclick="togglePassword()">
<i class="fas fa-eye" id="toggleIcon"></i>
</button>
</div>
<small class="text-muted">
Used when SSH key auth is not available on the target device.
</small>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" name="use_password_auth"
id="usePasswordAuth" role="switch"
{% if settings.get('use_password_auth') %}checked{% endif %}>
<label class="form-check-label fw-semibold" for="usePasswordAuth">
Use password authentication (instead of SSH keys)
</label>
<div class="text-muted small mt-1">
When enabled, Ansible will connect to all devices using the password above.
SSH key files will be ignored.
</div>
</div>
<button type="submit" class="btn btn-success">
<i class="fas fa-save me-1"></i>Save Settings
</button>
</form>
</div>
</div>
</div>
</div><!-- /row -->
<!-- Test Password row -->
<div class="row g-4 mt-1">
<div class="col-12">
<div class="card shadow-sm border-info">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="fas fa-vial me-2"></i>Test Password Authentication</h5>
</div>
<div class="card-body">
<p class="text-muted small mb-3">
Verify the password above works on a specific device <strong>before</strong> running the full key deployment.
This connects with password-only auth (no SSH key) so you get an accurate pre-flight result.
</p>
<div class="row g-2 align-items-end">
<div class="col-sm-5">
<label class="form-label fw-semibold mb-1">Device IP</label>
<input type="text" id="testIpInput" class="form-control"
placeholder="e.g. 10.76.157.145" autocomplete="off">
</div>
<div class="col-sm-4">
<label class="form-label fw-semibold mb-1">Password <span class="text-muted fw-normal">(leave blank to use saved)</span></label>
<input type="password" id="testPasswordInput" class="form-control"
placeholder="Uses saved password if empty" autocomplete="off">
</div>
<div class="col-sm-3">
<button id="testPasswordBtn" class="btn btn-info w-100" onclick="testPasswordAuth()">
<i class="fas fa-plug me-1"></i>Test Connection
</button>
</div>
</div>
<div id="testPasswordResult" class="mt-3" style="display:none;"></div>
</div>
</div>
</div>
</div>
<!-- Deploy SSH Keys row -->
<div class="row g-4 mt-1">
<div class="col-12">
<div class="card shadow-sm border-warning">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0"><i class="fas fa-rocket me-2"></i>Deploy SSH Keys to All Devices</h5>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-lg-8">
<p class="mb-1">
Connects to every device <strong>using the password</strong> configured above
and copies <code>~/.ssh/id_rsa.pub</code> into each device's
<code>~/.ssh/authorized_keys</code>.
</p>
<p class="text-muted small mb-0">
<i class="fas fa-info-circle me-1"></i>
Run this once to bootstrap key-based auth. Afterwards, disable
<em>"Use password authentication"</em> so all playbooks switch to SSH keys automatically.
</p>
</div>
<div class="col-lg-4 text-lg-end mt-3 mt-lg-0">
{% if not settings.get('ssh_fallback_password') %}
<div class="alert alert-warning py-2 mb-2 small">
<i class="fas fa-exclamation-triangle me-1"></i>
Set the SSH Password first, then save settings.
</div>
{% endif %}
<button id="deployKeysBtn" class="btn btn-warning btn-lg"
{% if not settings.get('ssh_fallback_password') %}disabled{% endif %}
onclick="deploySSHKeys()">
<i class="fas fa-key me-1"></i>Deploy SSH Keys to All Devices
</button>
</div>
</div>
<div id="deployStatusBar" class="mt-3" style="display:none;">
<div class="alert alert-info mb-0" id="deployStatusMsg">
<span class="spinner-border spinner-border-sm me-2"></span>Starting deployment…
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /container -->
<script>
function togglePassword() {
const input = document.getElementById('sshFallbackPassword');
const icon = document.getElementById('toggleIcon');
if (input.type === 'password') {
input.type = 'text';
icon.classList.replace('fa-eye', 'fa-eye-slash');
} else {
input.type = 'password';
icon.classList.replace('fa-eye-slash', 'fa-eye');
}
}
function testPasswordAuth() {
const ip = document.getElementById('testIpInput').value.trim();
const pw = document.getElementById('testPasswordInput').value;
const btn = document.getElementById('testPasswordBtn');
const result = document.getElementById('testPasswordResult');
if (!ip) {
result.style.display = '';
result.innerHTML = '<div class="alert alert-warning py-2 mb-0"><i class="fas fa-exclamation-triangle me-2"></i>Enter a device IP first.</div>';
return;
}
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Testing…';
result.style.display = '';
result.innerHTML = '<div class="alert alert-secondary py-2 mb-0"><span class="spinner-border spinner-border-sm me-2"></span>Connecting to ' + ip + '…</div>';
const body = { device_ip: ip };
if (pw) body.password = pw;
fetch('/api/ansible/ssh/test-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify(body)
})
.then(r => r.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-plug me-1"></i>Test Connection';
if (data.success) {
result.innerHTML = '<div class="alert alert-success py-2 mb-0"><i class="fas fa-check-circle me-2"></i>' + (data.message || 'Authentication succeeded!') + '</div>';
} else {
const reachable = data.reachable;
const icon = reachable === false ? 'fa-times-circle' : 'fa-key';
const cls = reachable === false ? 'alert-warning' : 'alert-danger';
result.innerHTML = '<div class="alert ' + cls + ' py-2 mb-0"><i class="fas ' + icon + ' me-2"></i>' + (data.error || 'Connection failed') + '</div>';
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-plug me-1"></i>Test Connection';
result.innerHTML = '<div class="alert alert-danger py-2 mb-0"><i class="fas fa-times-circle me-2"></i>Network error: ' + err + '</div>';
});
}
function deploySSHKeys() {
const btn = document.getElementById('deployKeysBtn');
const bar = document.getElementById('deployStatusBar');
const msg = document.getElementById('deployStatusMsg');
if (!confirm('Deploy the server SSH public key to ALL devices using the configured password?\n\nThis will add the key to each device\'s authorized_keys file.')) return;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Deploying…';
bar.style.display = '';
msg.className = 'alert alert-info mb-0';
msg.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Starting deployment…';
fetch('/api/ansible/ssh/distribute-keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({})
})
.then(r => r.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-key me-1"></i>Deploy SSH Keys to All Devices';
if (data.success) {
const execId = data.execution_id;
const popupUrl = `/ansible/executions/${execId}/live-popup`;
const detailUrl = `/ansible/executions/${execId}`;
window.open(popupUrl, `deploy_keys_${execId}`,
'width=960,height=660,resizable=yes,scrollbars=no,toolbar=no,menubar=no');
msg.className = 'alert alert-success mb-0';
msg.innerHTML = `<i class="fas fa-check-circle me-2"></i>Deployment started. ` +
`<a href="${popupUrl}" target="_blank">Open live output</a> &nbsp;|&nbsp; ` +
`<a href="${detailUrl}">View details</a>`;
} else {
msg.className = 'alert alert-danger mb-0';
msg.innerHTML = `<i class="fas fa-times-circle me-2"></i>Error: ${data.error || 'Unknown error'}`;
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-key me-1"></i>Deploy SSH Keys to All Devices';
msg.className = 'alert alert-danger mb-0';
msg.innerHTML = `<i class="fas fa-times-circle me-2"></i>Network error: ${err}`;
});
}
</script>
{% endblock %}