Files
pvemanager/backend/app/templates/ipam_history.html

184 lines
7.9 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ t('ipam_history') }}{% endblock %}
{% block content %}
<div class="page-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<div>
<h1 style="font-size: 1.75rem; font-weight: 600; margin: 0;"><i class="fa-solid fa-file-lines"></i> {{ t('ipam_history') }}</h1>
<p style="color: var(--text-secondary); margin-top: 0.25rem;">{{ t('all_changes_log') }}</p>
</div>
<div style="display: flex; gap: 0.75rem;">
<a href="/ipam" class="btn" style="background: var(--bg-tertiary); color: var(--text-primary);">← {{ t('back') }}</a>
<button onclick="loadHistory()" class="btn" style="background: var(--bg-tertiary); color: var(--text-primary);"><i class="fa-solid fa-rotate"></i> {{ t('refresh') }}</button>
</div>
</div>
<!-- Filters -->
<div class="card" style="margin-bottom: 1rem;">
<div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;">
<div>
<label style="color: var(--text-secondary); font-size: 0.875rem;">{{ t('action_filter') }}</label>
<select id="filterAction" onchange="loadHistory()" style="padding: 0.5rem; border-radius: 6px; border: 1px solid var(--border-color); background: var(--bg-tertiary); color: var(--text-primary);">
<option value="">{{ t('all') }}</option>
<option value="allocate">{{ t('allocation') }}</option>
<option value="release">{{ t('release') }}</option>
<option value="reserve">{{ t('reservation') }}</option>
<option value="update">{{ t('update') }}</option>
</select>
</div>
<div>
<label style="color: var(--text-secondary); font-size: 0.875rem;">{{ t('period_filter') }}</label>
<select id="filterPeriod" onchange="loadHistory()" style="padding: 0.5rem; border-radius: 6px; border: 1px solid var(--border-color); background: var(--bg-tertiary); color: var(--text-primary);">
<option value="7">{{ t('days_7') }}</option>
<option value="30" selected>{{ t('days_30') }}</option>
<option value="90">{{ t('days_90') }}</option>
<option value="">{{ t('all_time') }}</option>
</select>
</div>
</div>
</div>
<!-- History List -->
<div class="card">
<div id="historyContainer">
<div style="text-align: center; padding: 2rem; color: var(--text-muted);">
<i class="fa-solid fa-spinner fa-spin"></i> {{ t('loading_ellipsis') }}
</div>
</div>
</div>
<script>
// URL params helper functions
function getUrlParams() {
return new URLSearchParams(window.location.search);
}
function updateUrlParams(params) {
const url = new URL(window.location);
Object.entries(params).forEach(([key, value]) => {
if (value) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
});
window.history.replaceState({}, '', url);
}
function restoreFiltersFromUrl() {
const params = getUrlParams();
const action = params.get('action') || '';
document.getElementById('filterAction').value = action;
const period = params.get('period') || '30';
document.getElementById('filterPeriod').value = period;
}
async function loadHistory() {
try {
const action = document.getElementById('filterAction').value;
const period = document.getElementById('filterPeriod').value;
// Save filters to URL
updateUrlParams({ action: action, period: period });
let url = '/ipam/api/history?limit=100';
if (action) url += `&action=${action}`;
if (period) {
const date = new Date();
date.setDate(date.getDate() - parseInt(period));
url += `&from_date=${date.toISOString()}`;
}
const response = await fetchWithAuth(url);
const container = document.getElementById('historyContainer');
if (response.ok) {
const history = await response.json();
renderHistory(history);
} else {
container.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--danger);"><i class="fa-solid fa-circle-xmark"></i> ' + t('error_loading') + '</div>';
}
} catch (error) {
console.error('Error loading history:', error);
document.getElementById('historyContainer').innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--danger);"><i class="fa-solid fa-circle-xmark"></i> ' + t('error_loading') + '</div>';
}
}
function renderHistory(history) {
const container = document.getElementById('historyContainer');
if (history.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 3rem;">
<div style="font-size: 3rem; margin-bottom: 1rem;"><i class="fa-solid fa-scroll"></i></div>
<h3 style="color: var(--text-primary); margin-bottom: 0.5rem;">${t('history_empty')}</h3>
<p style="color: var(--text-secondary);">${t('no_records_for_period')}</p>
</div>
`;
return;
}
const actionIcons = {
'allocate': '<i class="fa-solid fa-plus"></i>',
'release': '<i class="fa-solid fa-minus"></i>',
'reserve': '<i class="fa-solid fa-lock"></i>',
'update': '<i class="fa-solid fa-pen"></i>',
'create': '<i class="fa-solid fa-plus-circle"></i>',
'delete': '<i class="fa-solid fa-trash"></i>'
};
const actionColors = {
'allocate': '#22c55e',
'release': '#ef4444',
'reserve': '#4f46e5',
'update': '#3b82f6',
'create': '#22c55e',
'delete': '#ef4444'
};
container.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
${history.map(h => `
<div style="display: flex; align-items: flex-start; gap: 1rem; padding: 1rem; border-radius: 8px; background: var(--bg-secondary); border-left: 3px solid ${actionColors[h.action] || 'var(--border-color)'};">
<div style="font-size: 1.5rem;">${actionIcons[h.action] || '<i class="fa-solid fa-file-lines"></i>'}</div>
<div style="flex: 1;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<span style="font-family: monospace; background: var(--bg-tertiary); padding: 0.25rem 0.5rem; border-radius: 4px; font-weight: 500; color: var(--text-primary);">${h.ip_address}</span>
<span style="color: var(--text-secondary); margin-left: 0.5rem;">${h.network_name || ''}</span>
</div>
<span style="color: var(--text-muted); font-size: 0.875rem;">${new Date(h.created_at).toLocaleString('ru-RU')}</span>
</div>
<div style="margin-top: 0.5rem; color: var(--text-secondary);">
${h.details || getActionDescription(h.action)}
</div>
${h.performed_by ? `<div style="margin-top: 0.25rem; color: var(--text-muted); font-size: 0.75rem;"><i class="fa-solid fa-user"></i> ${h.performed_by}</div>` : ''}
</div>
</div>
`).join('')}
</div>
`;
}
function getActionDescription(action) {
const descriptions = {
'allocate': t('ip_allocated'),
'release': t('ip_released'),
'reserve': t('ip_reserved'),
'update': t('record_updated'),
'create': t('record_created'),
'delete': t('record_deleted')
};
return descriptions[action] || action;
}
// Initial load after DOM ready
window.addEventListener('DOMContentLoaded', () => {
restoreFiltersFromUrl();
setTimeout(loadHistory, 100);
});
</script>
{% endblock %}