298 lines
14 KiB
HTML
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> | ` +
|
|
`<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 %}
|