feat: execution failure reports, auto-printer for WMT, UTC timezone fix for all timestamps

This commit is contained in:
ske087
2026-04-24 15:52:12 +03:00
parent d2485e4c66
commit 056f467791
27 changed files with 1391 additions and 285 deletions

View File

@@ -63,18 +63,18 @@
</div>
<div class="card-body">
<p class="text-muted small">
When key-based authentication fails, the server falls back to password auth.
Set the default password for devices on this network below.
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 Fallback Password</label>
<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 fallback password"
placeholder="Enter device password"
required>
<button class="btn btn-outline-secondary" type="button"
onclick="togglePassword()">
@@ -86,6 +86,19 @@
</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>
@@ -95,6 +108,87 @@
</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>
@@ -109,5 +203,95 @@ function togglePassword() {
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 %}