Files
Server_Monitorizare_v2/templates/ansible/playbooks.html
2026-04-23 15:55:46 +03:00

685 lines
26 KiB
HTML

{% extends "base.html" %}
{% block title %}Ansible Playbook Management - Server Monitoring{% endblock %}
{% block page_title %}Ansible Playbook Management{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/theme/monokai.min.css">
<style>
.playbook-item {
cursor: pointer;
transition: all 0.2s;
}
.playbook-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.playbook-item.selected {
border-color: #007bff;
background-color: #f8f9ff;
}
.code-editor-area {
min-height: 400px;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
}
.CodeMirror {
height: 400px;
border-radius: 0.375rem;
}
.playbook-actions {
position: sticky;
top: 0;
z-index: 10;
background: white;
border-bottom: 1px solid #dee2e6;
padding: 1rem;
margin: -1rem -1rem 1rem -1rem;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header Actions -->
<div class="row mb-4">
<div class="col-md-8">
<h4><i class="fas fa-book"></i> Ansible Playbook Management</h4>
<small class="text-muted">Create, edit, and manage your Ansible automation playbooks</small>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-success me-2" onclick="createNewPlaybook()">
<i class="fas fa-plus"></i> New Playbook
</button>
<button class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#uploadPlaybookModal">
<i class="fas fa-upload"></i> Import File
</button>
<button class="btn btn-outline-secondary" onclick="refreshPlaybooks()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
</div>
<!-- Main Layout -->
<div class="row">
<!-- Playbook List Sidebar -->
<div class="col-lg-4">
<!-- Playbook Statistics -->
<div class="row mb-3">
<div class="col-6">
<div class="card bg-primary text-white text-center">
<div class="card-body py-2">
<h6 class="mb-0">Custom</h6>
<h4 class="mb-0">{{ playbooks | length }}</h4>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-success text-white text-center">
<div class="card-body py-2">
<h6 class="mb-0">Built-in</h6>
<h4 class="mb-0">{{ builtin_playbooks | length }}</h4>
</div>
</div>
</div>
</div>
<!-- Built-in Playbooks Section -->
{% if builtin_playbooks %}
<div class="card mb-3">
<div class="card-header py-2">
<h6 class="mb-0"><i class="fas fa-star text-warning"></i> Built-in Playbooks</h6>
</div>
<div class="card-body p-2">
{% for playbook in builtin_playbooks %}
<div class="playbook-item card mb-2 border-success" onclick="loadBuiltinPlaybook('{{ playbook.name }}')">
<div class="card-body py-2">
<h6 class="card-title mb-1">{{ playbook.name }}</h6>
<p class="card-text small text-muted mb-1">{{ playbook.description }}</p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-success">Built-in</span>
<a href="{{ url_for('ansible_web.execute') }}?playbook={{ playbook.name }}"
class="btn btn-sm btn-outline-primary"
onclick="event.stopPropagation()">
<i class="fas fa-play me-1"></i>Execute
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Custom Playbooks Section -->
<div class="card">
<div class="card-header py-2">
<h6 class="mb-0"><i class="fas fa-file-code"></i> Custom Playbooks</h6>
</div>
<div class="card-body p-2">
{% if playbooks %}
{% for playbook in playbooks %}
<div class="playbook-item card mb-2" onclick="loadPlaybook('{{ playbook.name }}', '{{ playbook.path }}')">
<div class="card-body py-2">
<h6 class="card-title mb-1">{{ playbook.name }}</h6>
<p class="card-text small text-muted mb-1">{{ playbook.filename }}</p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-info">Custom</span>
<div class="btn-group" role="group">
<a href="{{ url_for('ansible_web.execute') }}?playbook={{ playbook.name }}"
class="btn btn-sm btn-outline-primary"
onclick="event.stopPropagation()">
<i class="fas fa-play"></i>
</a>
<button class="btn btn-sm btn-outline-danger" onclick="event.stopPropagation(); deletePlaybook('{{ playbook.name }}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-3">
<i class="fas fa-folder-open fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No custom playbooks</p>
<small class="text-muted">Create or import one to get started</small>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Code Editor Area -->
<div class="col-lg-8">
<div class="card h-100">
<div class="playbook-actions">
<div class="row align-items-center">
<div class="col">
<h6 class="mb-0" id="currentPlaybookTitle">Select a playbook to view/edit</h6>
<small class="text-muted" id="currentPlaybookInfo">Choose from the list on the left</small>
</div>
<div class="col-auto">
<div id="editorActions" style="display: none;">
<button class="btn btn-success btn-sm me-2" onclick="savePlaybook()">
<i class="fas fa-save"></i> Save
</button>
<button class="btn btn-outline-secondary btn-sm me-2" onclick="toggleEditMode()">
<i class="fas fa-edit"></i> <span id="editToggleText">Edit</span>
</button>
<button class="btn btn-primary btn-sm me-2" onclick="executeCurrentPlaybook()">
<i class="fas fa-play"></i> Execute
</button>
<button class="btn btn-outline-warning btn-sm" onclick="validatePlaybook()">
<i class="fas fa-check"></i> Validate
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<!-- Welcome Message (shown when no playbook selected) -->
<div id="welcomeMessage" class="text-center p-5">
<i class="fas fa-book-open fa-4x text-muted mb-3"></i>
<h4 class="text-muted">Ansible Playbook Editor</h4>
<p class="text-muted mb-4">Select a playbook from the left panel to view or edit its content</p>
<div class="d-flex justify-content-center gap-2">
<button class="btn btn-primary" onclick="createNewPlaybook()">
<i class="fas fa-plus"></i> Create New Playbook
</button>
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#uploadPlaybookModal">
<i class="fas fa-upload"></i> Import Playbook File
</button>
</div>
</div>
<!-- Code Editor -->
<div id="codeEditorContainer" style="display: none;">
<textarea id="playbookEditor"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Upload Playbook Modal -->
<div class="modal fade" id="uploadPlaybookModal" tabindex="-1" aria-labelledby="uploadPlaybookModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="uploadPlaybookModalLabel">Upload Playbook</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url_for('ansible_web.upload_playbook') }}" method="post" enctype="multipart/form-data">
<div class="modal-body">
<div class="mb-3">
<label for="playbookFile" class="form-label">Select Playbook File (.yml or .yaml)</label>
<input type="file" class="form-control" id="playbookFile" name="playbook_file"
accept=".yml,.yaml" required>
</div>
<div class="mb-3">
<label for="playbookName" class="form-label">Playbook Name (optional)</label>
<input type="text" class="form-control" id="playbookName" name="playbook_name"
placeholder="Leave empty to use filename">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
</div>
</div>
</div>
<!-- View Playbook Modal -->
<div class="modal fade" id="viewPlaybookModal" tabindex="-1" aria-labelledby="viewPlaybookModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewPlaybookModalLabel">Playbook Content</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<pre id="playbookContent" style="max-height: 400px; overflow-y: auto; background-color: #f8f9fa; padding: 15px; border-radius: 5px;"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Create/Edit Playbook Modal -->
<div class="modal fade" id="createPlaybookModal" tabindex="-1" aria-labelledby="createPlaybookModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createPlaybookModalLabel">Create New Playbook</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="newPlaybookName" class="form-label">Playbook Name</label>
<input type="text" class="form-control" id="newPlaybookName" placeholder="my_playbook" required>
<div class="form-text">Name should be lowercase, use underscores instead of spaces</div>
</div>
<div class="mb-3">
<label for="newPlaybookDescription" class="form-label">Description (optional)</label>
<input type="text" class="form-control" id="newPlaybookDescription" placeholder="Brief description of what this playbook does">
</div>
<div class="mb-3">
<label class="form-label">Template</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="playbookTemplate" id="templateBlank" value="blank" checked>
<label class="form-check-label" for="templateBlank">
Blank Playbook
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="playbookTemplate" id="templateBasic" value="basic">
<label class="form-check-label" for="templateBasic">
Basic System Update Template
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="playbookTemplate" id="templateService" value="service">
<label class="form-check-label" for="templateService">
Service Management Template
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createPlaybookFromModal()">Create Playbook</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/mode/yaml/yaml.min.js"></script>
<script>
let codeEditor = null;
let isEditMode = false;
let currentPlaybook = null;
let isNewPlaybook = false;
// Initialize CodeMirror when document is ready
document.addEventListener('DOMContentLoaded', function() {
initializeCodeEditor();
});
function initializeCodeEditor() {
codeEditor = CodeMirror.fromTextArea(document.getElementById('playbookEditor'), {
lineNumbers: true,
mode: 'yaml',
theme: 'monokai',
indentUnit: 2,
tabSize: 2,
indentWithTabs: false,
readOnly: true, // Start in read-only mode
lineWrapping: true
});
}
function createNewPlaybook() {
const modal = new bootstrap.Modal(document.getElementById('createPlaybookModal'));
modal.show();
}
function createPlaybookFromModal() {
const name = document.getElementById('newPlaybookName').value.trim();
const description = document.getElementById('newPlaybookDescription').value.trim();
const template = document.querySelector('input[name="playbookTemplate"]:checked').value;
if (!name) {
alert('Please enter a playbook name');
return;
}
// Generate playbook content based on template
let content = '';
switch(template) {
case 'basic':
content = '---\n' +
'- name: ' + (description || 'Basic System Update') + '\n' +
' hosts: monitoring_devices\n' +
' become: yes\n' +
' vars:\n' +
' update_cache: yes\n' +
' upgrade_packages: yes\n' +
' \n' +
' tasks:\n' +
' - name: Update package cache\n' +
' apt:\n' +
' update_cache: "\\{\\{ update_cache \\}\\}"\n' +
' when: ansible_os_family == "Debian"\n' +
' \n' +
' - name: Upgrade all packages\n' +
' apt:\n' +
' upgrade: dist\n' +
' when: upgrade_packages and ansible_os_family == "Debian"\n' +
' \n' +
' - name: Remove unnecessary packages\n' +
' apt:\n' +
' autoremove: yes\n' +
' when: ansible_os_family == "Debian"\n' +
' \n' +
' - name: Display completion message\n' +
' debug:\n' +
' msg: "System update completed successfully"';
break;
case 'service':
content = '---\n' +
'- name: ' + (description || 'Service Management') + '\n' +
' hosts: monitoring_devices\n' +
' become: yes\n' +
' vars:\n' +
' service_name: "your_service_here"\n' +
' service_action: "restarted" # started, stopped, restarted, reloaded\n' +
' \n' +
' tasks:\n' +
' - name: Manage service\n' +
' systemd:\n' +
' name: "\\{\\{ service_name \\}\\}"\n' +
' state: "\\{\\{ service_action \\}\\}"\n' +
' enabled: yes\n' +
' register: service_result\n' +
' \n' +
' - name: Display service status\n' +
' debug:\n' +
' msg: "Service \\{\\{ service_name \\}\\} is \\{\\{ service_result.status.ActiveState \\}\\}"';
break;
default:
content = '---\n' +
'- name: ' + (description || name) + '\n' +
' hosts: monitoring_devices\n' +
' become: yes\n' +
' \n' +
' tasks:\n' +
' - name: Your task here\n' +
' debug:\n' +
' msg: "Hello from ' + name + ' playbook!"';
}
// Close modal
bootstrap.Modal.getInstance(document.getElementById('createPlaybookModal')).hide();
// Load into editor as new playbook
currentPlaybook = {name: name, isNew: true};
isNewPlaybook = true;
loadPlaybookIntoEditor(name, content, true);
}
function loadPlaybook(name, path) {
// Clear selection
clearPlaybookSelection();
// Mark as selected
event.currentTarget.classList.add('selected');
fetch(`/ansible/playbook/content?path=${encodeURIComponent(path)}`)
.then(response => response.text())
.then(content => {
currentPlaybook = {name: name, path: path};
isNewPlaybook = false;
loadPlaybookIntoEditor(name, content, false);
})
.catch(error => {
alert('Error loading playbook: ' + error);
});
}
function loadBuiltinPlaybook(name) {
// Clear selection
clearPlaybookSelection();
// Mark as selected
event.currentTarget.classList.add('selected');
// Generate built-in playbook content
let content = generateBuiltinPlaybookContent(name);
currentPlaybook = {name: name, builtin: true};
isNewPlaybook = false;
loadPlaybookIntoEditor(name, content, false);
}
function loadPlaybookIntoEditor(name, content, editMode = false) {
// Update UI
document.getElementById('welcomeMessage').style.display = 'none';
document.getElementById('codeEditorContainer').style.display = 'block';
document.getElementById('editorActions').style.display = 'block';
// Update title
document.getElementById('currentPlaybookTitle').textContent = name;
document.getElementById('currentPlaybookInfo').textContent =
isNewPlaybook ? 'New playbook - Remember to save' :
(currentPlaybook.builtin ? 'Built-in playbook (read-only)' : 'Custom playbook');
// Set content
codeEditor.setValue(content);
// Set edit mode
isEditMode = editMode || isNewPlaybook;
updateEditMode();
// Refresh editor
setTimeout(() => codeEditor.refresh(), 100);
}
function generateBuiltinPlaybookContent(name) {
switch(name) {
case 'update_devices':
return `---
- name: Update all monitoring devices
hosts: monitoring_devices
become: yes
tasks:
- name: Update package cache
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: Upgrade packages
apt:
upgrade: dist
when: ansible_os_family == "Debian"
- name: Remove unnecessary packages
apt:
autoremove: yes
when: ansible_os_family == "Debian"`;
case 'restart_service':
return `---
- name: Restart monitoring services
hosts: monitoring_devices
become: yes
tasks:
- name: Restart monitoring service
systemd:
name: prezenta_monitor
state: restarted
ignore_errors: yes`;
case 'system_health':
return `---
- name: Check system health
hosts: monitoring_devices
become: yes
tasks:
- name: Check disk usage
command: df -h
register: disk_usage
- name: Check memory usage
command: free -m
register: memory_usage
- name: Display disk usage
debug:
var: disk_usage.stdout_lines
- name: Display memory usage
debug:
var: memory_usage.stdout_lines`;
default:
return `---
- name: ${name}
hosts: monitoring_devices
become: yes
tasks:
- name: Default task
debug:
msg: "Built-in playbook: ${name}"`;
}
}
function clearPlaybookSelection() {
document.querySelectorAll('.playbook-item').forEach(item => {
item.classList.remove('selected');
});
}
function toggleEditMode() {
if (currentPlaybook && currentPlaybook.builtin) {
alert('Built-in playbooks cannot be edited. Create a copy if you need to modify it.');
return;
}
isEditMode = !isEditMode;
updateEditMode();
}
function updateEditMode() {
codeEditor.setOption('readOnly', !isEditMode);
document.getElementById('editToggleText').textContent = isEditMode ? 'View' : 'Edit';
// Update editor theme
codeEditor.setOption('theme', isEditMode ? 'default' : 'monokai');
}
function savePlaybook() {
if (!currentPlaybook) {
alert('No playbook loaded');
return;
}
if (currentPlaybook.builtin) {
alert('Built-in playbooks cannot be modified');
return;
}
const content = codeEditor.getValue();
const data = {
name: currentPlaybook.name,
content: content,
is_new: isNewPlaybook
};
fetch('/ansible/playbook/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert('Playbook saved successfully!');
isNewPlaybook = false;
document.getElementById('currentPlaybookInfo').textContent = 'Custom playbook';
// Optionally refresh the page to update the playbook list
setTimeout(() => location.reload(), 1000);
} else {
alert('Error saving playbook: ' + result.error);
}
})
.catch(error => {
alert('Error saving playbook: ' + error);
});
}
function executeCurrentPlaybook() {
if (!currentPlaybook) {
alert('No playbook selected');
return;
}
window.location.href = `{{ url_for('ansible_web.execute') }}?playbook=${encodeURIComponent(currentPlaybook.name)}`;
}
function validatePlaybook() {
if (!codeEditor) {
alert('No playbook loaded');
return;
}
const content = codeEditor.getValue();
fetch('/ansible/playbook/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({content: content})
})
.then(response => response.json())
.then(result => {
if (result.valid) {
alert('Playbook is valid! ✅');
} else {
alert('Playbook validation failed: ' + result.error);
}
})
.catch(error => {
alert('Error validating playbook: ' + error);
});
}
function refreshPlaybooks() {
location.reload();
}
function deletePlaybook(playbookName) {
if (confirm(`Are you sure you want to delete the playbook "${playbookName}"?`)) {
fetch(`/ansible/playbook/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({playbook_name: playbookName})
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert('Playbook deleted successfully!');
location.reload();
} else {
alert('Error deleting playbook: ' + result.error);
}
})
.catch(error => {
alert('Error deleting playbook: ' + error);
});
}
}
</script>
{% endblock %}
</script>