Initial commit — Server_Monitorizare_v2

This commit is contained in:
ske087
2026-04-23 15:55:46 +03:00
commit d2485e4c66
61 changed files with 13861 additions and 0 deletions

View File

@@ -0,0 +1,664 @@
{% extends "base.html" %}
{% block title %}Ansible Management Dashboard - Server Monitoring{% endblock %}
{% block page_title %}Ansible Management Dashboard{% endblock %}
{% block extra_css %}
<style>
.card-stat {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.card-stat:hover {
transform: translateY(-5px);
}
.card-stat h3 {
font-size: 2.5rem;
font-weight: bold;
}
.device-card {
border: none;
border-radius: 12px;
box-shadow: 0 2px 15px rgba(0,0,0,0.08);
transition: all 0.3s ease;
cursor: pointer;
}
.device-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
}
.device-card.selected {
border: 2px solid #28a745;
background-color: #f8fff9;
}
.playbook-card {
border: none;
border-radius: 12px;
box-shadow: 0 2px 15px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}
.playbook-card:hover {
transform: translateY(-3px);
}
.execution-card {
border: none;
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.execution-card:hover {
transform: translateY(-5px);
}
.status-running { color: #0d6efd; }
.status-completed { color: #198754; }
.status-failed { color: #dc3545; }
/* Tab styles */
.nav-tabs .nav-link {
border: none;
border-radius: 25px 25px 0 0;
margin-right: 5px;
padding: 12px 20px;
font-weight: 500;
color: #6c757d;
background-color: #f8f9fa;
}
.nav-tabs .nav-link:hover {
background-color: #e9ecef;
color: #495057;
}
.nav-tabs .nav-link.active {
background-color: #3498db;
color: white;
border: none;
}
.tab-content {
border: 1px solid #dee2e6;
border-top: none;
border-radius: 0 15px 15px 15px;
background-color: white;
min-height: 400px;
}
/* Button styles */
.btn-custom {
border-radius: 25px;
padding: 10px 20px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-custom:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
</style>
{% 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 %}
<!-- Ansible Management Tabs -->
<div class="row mb-4">
<div class="col-12">
<ul class="nav nav-tabs" id="ansibleTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="automation-tab" data-bs-toggle="tab" data-bs-target="#automation" type="button" role="tab">
<i class="fas fa-robot"></i> Automation Overview
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="devices-tab" data-bs-toggle="tab" data-bs-target="#devices" type="button" role="tab">
<i class="fas fa-network-wired"></i> Remote Devices
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="playbooks-tab" data-bs-toggle="tab" data-bs-target="#playbooks" type="button" role="tab">
<i class="fas fa-play"></i> Playbook Management
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="execution-tab" data-bs-toggle="tab" data-bs-target="#execution" type="button" role="tab">
<i class="fas fa-cogs"></i> Execution
</button>
</li>
</ul>
</div>
</div>
<!-- Tab Content -->
<div class="tab-content" id="ansibleTabContent">
<!-- Automation Overview Tab -->
<div class="tab-pane fade show active" id="automation" role="tabpanel">
<div class="row mb-4">
<div class="col-md-3">
<div class="card card-stat">
<div class="card-body text-center">
<i class="fas fa-server fa-2x mb-2"></i>
<h3>{{ stats.get('total_devices', 0) }}</h3>
<p>Devices</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card card-stat" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<div class="card-body text-center">
<i class="fas fa-play-circle fa-2x mb-2"></i>
<h3>{{ ((playbooks|default([])|length) + (builtin_playbooks|default([])|length)) }}</h3>
<p>Playbooks</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card card-stat" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<div class="card-body text-center">
<i class="fas fa-tasks fa-2x mb-2"></i>
<h3>{{ (executions|default([]))|length }}</h3>
<p>Executions</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card card-stat" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<div class="card-body text-center">
<i class="fas fa-check-circle fa-2x mb-2"></i>
<h3>{{ (executions|default([]) | selectattr('status', 'equalto', 'completed') | list | length) }}</h3>
<p>Success Rate</p>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5>Quick Actions</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<button class="btn btn-primary btn-custom w-100" onclick="executeQuickPlaybook()">
<i class="fas fa-play"></i> Quick Execute
</button>
</div>
<div class="col-md-3">
<button class="btn btn-success btn-custom w-100" onclick="refreshAll()">
<i class="fas fa-sync"></i> Refresh All
</button>
</div>
<div class="col-md-3">
<button class="btn btn-info btn-custom w-100" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="fas fa-plus"></i> Add Device
</button>
</div>
<div class="col-md-3">
<button class="btn btn-warning btn-custom w-100" data-bs-toggle="modal" data-bs-target="#uploadPlaybookModal">
<i class="fas fa-upload"></i> Upload Playbook
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Remote Devices Tab -->
<div class="tab-pane fade" id="devices" role="tabpanel">
<div class="row mb-4">
<div class="col-md-8">
<h4>Managed Devices</h4>
<small class="text-muted">Configure and manage devices through Ansible</small>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-primary btn-custom" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="fas fa-plus"></i> Add Device
</button>
<button class="btn btn-success btn-custom" onclick="refreshInventory()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
</div>
<!-- Device Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">Total Devices</h5>
<h2>{{ (devices|default([]))|length }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Online</h5>
<h2>{{ (devices|default([]) | selectattr('status', 'equalto', 'active') | list | length) }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<h5 class="card-title">Offline</h5>
<h2>{{ (devices|default([]) | selectattr('status', 'equalto', 'inactive') | list | length) }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Groups</h5>
<h2>{{ (device_groups|default([]))|length }}</h2>
</div>
</div>
</div>
</div>
<!-- Devices Table -->
<div class="card">
<div class="card-header">
<h5>Device List</h5>
</div>
<div class="card-body">
{% if devices|default([]) %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Host</th>
<th>Group</th>
<th>Status</th>
<th>Last Check</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for device in devices|default([]) %}
<tr>
<td>{{ device.name }}</td>
<td>{{ device.host }}</td>
<td><span class="badge bg-secondary">{{ device.group }}</span></td>
<td>
{% if device.status == 'active' %}
<span class="badge bg-success">Online</span>
{% else %}
<span class="badge bg-danger">Offline</span>
{% endif %}
</td>
<td>{{ device.last_check.strftime('%Y-%m-%d %H:%M') if device.last_check else 'Never' }}</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="testDevice('{{ device.name }}')">
<i class="fas fa-plug"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="removeDevice('{{ device.name }}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-server fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No devices configured</h5>
<p class="text-muted">Add your first device to start managing infrastructure.</p>
<button class="btn btn-primary btn-custom" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="fas fa-plus"></i> Add Device
</button>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Playbook Management Tab -->
<div class="tab-pane fade" id="playbooks" role="tabpanel">
<div class="row mb-4">
<div class="col-md-8">
<h4>Playbook Management</h4>
<small class="text-muted">Manage and execute Ansible playbooks</small>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-primary btn-custom" data-bs-toggle="modal" data-bs-target="#uploadPlaybookModal">
<i class="fas fa-upload"></i> Upload Playbook
</button>
<button class="btn btn-success btn-custom" onclick="refreshPlaybooks()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
</div>
<!-- Playbook Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">Total Playbooks</h5>
<h2>{{ ((playbooks|default([]))|length + (builtin_playbooks|default([]))|length) }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Custom</h5>
<h2>{{ (playbooks|default([]))|length }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Built-in</h5>
<h2>{{ (builtin_playbooks|default([]))|length }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<h5 class="card-title">Recent Runs</h5>
<h2>{{ (executions|default([]))|length }}</h2>
</div>
</div>
</div>
</div>
<!-- Playbooks Grid -->
<div class="row">
{% if (builtin_playbooks|default([])) or (playbooks|default([])) %}
{% for playbook in (builtin_playbooks|default([])) + (playbooks|default([])) %}
<div class="col-md-6 col-lg-4 mb-3">
<div class="card playbook-card">
<div class="card-body">
<h6 class="card-title">{{ playbook.name }}</h6>
<p class="card-text text-muted small">{{ playbook.description | default('No description available') }}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
{% if playbook.type == 'builtin' %}
<span class="badge bg-primary">Built-in</span>
{% else %}
<span class="badge bg-info">Custom</span>
{% endif %}
</small>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="viewPlaybook('{{ playbook.name }}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-outline-success" onclick="executePlaybook('{{ playbook.name }}')">
<i class="fas fa-play"></i>
</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-play fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No playbooks available</h5>
<p class="text-muted">Upload a custom playbook to get started.</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadPlaybookModal">
<i class="fas fa-upload"></i> Upload Playbook
</button>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Execution Tab -->
<div class="tab-pane fade" id="execution" role="tabpanel">
<div class="row mb-4">
<div class="col-md-8">
<h4>Playbook Execution</h4>
<small class="text-muted">Execute playbooks on selected devices</small>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-primary btn-custom" onclick="executeQuickPlaybook()">
<i class="fas fa-play"></i> Quick Execute
</button>
</div>
</div>
<!-- Recent Executions -->
<div class="card">
<div class="card-header">
<h5>Recent Executions</h5>
</div>
<div class="card-body">
{% if executions|default([]) %}
<div class="row">
{% for execution in (executions|default([]))[:6] %}
<div class="col-md-6 mb-3">
<div class="card execution-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="card-title mb-0">{{ execution.playbook_name }}</h6>
<span class="badge
{% if execution.status == 'running' %}bg-primary status-running{% endif %}
{% if execution.status == 'completed' %}bg-success status-completed{% endif %}
{% if execution.status == 'failed' %}bg-danger status-failed{% endif %}">
{{ execution.status | title }}
</span>
</div>
<p class="card-text text-muted small">
<i class="fas fa-clock"></i>
{{ execution.start_time.strftime('%Y-%m-%d %H:%M:%S') if execution.start_time else 'N/A' }}
</p>
<div class="row text-center">
<div class="col">
<small class="text-success">
<i class="fas fa-check"></i> {{ execution.successful_hosts or 0 }}
</small>
</div>
<div class="col">
<small class="text-danger">
<i class="fas fa-times"></i> {{ execution.failed_hosts or 0 }}
</small>
</div>
<div class="col">
<small class="text-warning">
<i class="fas fa-question"></i> {{ execution.unreachable_hosts or 0 }}
</small>
</div>
</div>
<div class="mt-2">
<button class="btn btn-outline-primary btn-sm" onclick="viewExecution('{{ execution.id }}')">
<i class="fas fa-eye"></i> Details
</button>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-tasks fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No executions yet</h5>
<p class="text-muted">Execute your first playbook to see results here.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<!-- Add Device Modal -->
<div class="modal fade" id="addDeviceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Device</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addDeviceForm">
<div class="mb-3">
<label class="form-label">Device Name</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="mb-3">
<label class="form-label">Host/IP Address</label>
<input type="text" class="form-control" name="host" required>
</div>
<div class="mb-3">
<label class="form-label">Group</label>
<select class="form-control" name="group">
<option value="servers">Servers</option>
<option value="workstations">Workstations</option>
<option value="routers">Network Devices</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">SSH User</label>
<input type="text" class="form-control" name="user" value="ansible">
</div>
</form>
</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="addDevice()">Add Device</button>
</div>
</div>
</div>
</div>
<!-- Upload Playbook Modal -->
<div class="modal fade" id="uploadPlaybookModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Upload Playbook</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="uploadPlaybookForm" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Playbook File (.yml/.yaml)</label>
<input type="file" class="form-control" name="playbook_file" accept=".yml,.yaml" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" rows="3"></textarea>
</div>
</form>
</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="uploadPlaybook()">Upload</button>
</div>
</div>
</div>
</div>
<script>
function executeQuickPlaybook() {
// Implementation for quick playbook execution
alert('Quick execute functionality');
}
function refreshAll() {
location.reload();
}
function refreshInventory() {
// Implementation for refreshing inventory
alert('Refreshing inventory...');
}
function refreshPlaybooks() {
// Implementation for refreshing playbooks
alert('Refreshing playbooks...');
}
function testDevice(deviceName) {
// Implementation for testing device connection
alert('Testing connection to ' + deviceName);
}
function removeDevice(deviceName) {
if (confirm('Are you sure you want to remove device: ' + deviceName + '?')) {
// Implementation for removing device
alert('Removing device: ' + deviceName);
}
}
function viewPlaybook(playbookName) {
// Implementation for viewing playbook details
alert('Viewing playbook: ' + playbookName);
}
function executePlaybook(playbookName) {
// Implementation for executing playbook
alert('Executing playbook: ' + playbookName);
}
function viewExecution(executionId) {
// Implementation for viewing execution details
alert('Viewing execution: ' + executionId);
}
function addDevice() {
// Implementation for adding device
const form = document.getElementById('addDeviceForm');
const formData = new FormData(form);
// Convert to JSON and submit
alert('Adding device...');
// Close modal
bootstrap.Modal.getInstance(document.getElementById('addDeviceModal')).hide();
}
function uploadPlaybook() {
// Implementation for uploading playbook
const form = document.getElementById('uploadPlaybookForm');
const formData = new FormData(form);
alert('Uploading playbook...');
// Close modal
bootstrap.Modal.getInstance(document.getElementById('uploadPlaybookModal')).hide();
}
</script>
{% endblock %}