685 lines
26 KiB
HTML
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> |