refactor: extract inline CSS/JS from views into separate asset files (20+ files)

Phase A — HTML removed from PHP files:
- RateLimitMiddleware.php: heredoc moved to views/errors/429.php
- public/index.php: inline HTML replaced with View::error()
- AuthMiddleware.php: <script> redirect replaced with <meta refresh>

Phase B — Inline CSS/JS extracted from views:
- CSS: landing.css (459 lines), legal.css (shared by terms+privacy)
- JS: 17 new files extracted from ~20 view files
  - main.js (layout sidebar + notifications)
  - dashboard-chart.js, server-show.js, server-services.js
  - nginx-manager.js, database-manager.js
  - terminal.js, team-show.js, team-index.js
  - server-list.js, server-permissions.js (shared by edit+create)
  - server-ssl.js, server-processes.js, system-users.js
  - admin-users.js, audit-log.js, admin-notifications.js, admin-security.js
  - notifications.js

Total: -3264 lines from views, +288 lines in new assets
This commit is contained in:
2026-06-14 08:04:01 -04:00
parent 142fe304c9
commit 88f3c1f964
51 changed files with 3152 additions and 3269 deletions

View File

@@ -71,66 +71,4 @@
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
</form>
<script>
const allCards = document.querySelectorAll('.team-card');
function filterTeams(query) {
const q = query.toLowerCase().trim();
const grid = document.getElementById('teamsGrid');
const empty = document.getElementById('emptyState');
const clearBtn = document.getElementById('searchClear');
let visible = 0;
clearBtn.style.display = q ? 'block' : 'none';
if (grid) {
allCards.forEach(function(card) {
const name = card.dataset.name || '';
const desc = card.dataset.desc || '';
const match = !q || name.includes(q) || desc.includes(q);
card.style.display = match ? '' : 'none';
if (match) visible++;
});
if (visible === 0) {
if (!empty) {
const container = document.getElementById('teamsContainer');
const div = document.createElement('div');
div.className = 'empty-state';
div.id = 'emptyState';
div.innerHTML = '<i class="fas fa-users-cog"></i><p>No teams match your search.</p>';
container.appendChild(div);
} else {
empty.querySelector('p').textContent = 'No teams match your search.';
empty.style.display = '';
}
} else {
if (empty) empty.style.display = 'none';
}
}
// Update URL without reload
const url = q ? '?search=' + encodeURIComponent(query) : window.location.pathname;
window.history.replaceState({}, '', url);
}
document.getElementById('teamSearch')?.addEventListener('input', function() {
filterTeams(this.value);
});
// Restore search on page load
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('teamSearch');
if (searchInput && searchInput.value) {
filterTeams(searchInput.value);
}
});
function deleteTeam(id, name) {
if (confirm('Delete team "' + name + '"?\nThis will remove access for all members.')) {
const form = document.getElementById('deleteForm');
form.action = '/teams/' + id + '/delete';
form.submit();
}
}
</script>
<script src="/assets/js/team-index.js"></script>

View File

@@ -146,200 +146,8 @@ $availableServers = $availableServers ?? [];
</form>
<script>
function getCsrfToken() {
return document.querySelector('meta[name="csrf-token"]')?.content || '';
}
function editTeam() {
const name = prompt('Team name:', '<?= htmlspecialchars(addslashes($team['name'] ?? '')) ?>');
if (!name) return;
const desc = prompt('Description:', '<?= htmlspecialchars(addslashes($team['description'] ?? '')) ?>');
const form = new FormData();
form.append('name', name);
form.append('description', desc || '');
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/update', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) { showToast('success', res.message); location.reload(); }
else { showToast('error', res.message); }
});
}
function deleteTeam() {
if (!confirm('Delete this team? Members will lose access to assigned servers.')) return;
document.getElementById('postForm').action = '/teams/<?= $team['id'] ?>/delete';
document.getElementById('postForm').submit();
}
/* ───── Autocomplete para usuarios ───── */
let searchTimeout = null;
let selectedUserId = null;
document.getElementById('addUserInput').addEventListener('input', function() {
clearTimeout(searchTimeout);
const q = this.value.trim();
selectedUserId = null;
if (q.length < 2) {
document.getElementById('userDropdown').classList.remove('active');
return;
}
searchTimeout = setTimeout(function() {
fetch('/teams/search-users?q=' + encodeURIComponent(q), {
headers: { 'X-CSRF-Token': getCsrfToken() }
})
.then(r => r.json())
.then(data => {
const dropdown = document.getElementById('userDropdown');
dropdown.innerHTML = '';
if (data.users && data.users.length > 0) {
data.users.forEach(function(u) {
const item = document.createElement('div');
item.className = 'autocomplete-item';
item.innerHTML = '<strong>' + escapeHtml(u.username) + '</strong> <small>' + escapeHtml(u.email) + '</small>';
item.dataset.id = u.id;
item.dataset.username = u.username;
item.addEventListener('click', function() {
selectUser(u.id, u.username);
});
dropdown.appendChild(item);
});
dropdown.classList.add('active');
} else {
dropdown.innerHTML = '<div class="autocomplete-empty">No users found</div>';
dropdown.classList.add('active');
}
})
.catch(function() {
document.getElementById('userDropdown').classList.remove('active');
});
}, 300);
});
document.addEventListener('click', function(e) {
if (!e.target.closest('.autocomplete')) {
document.getElementById('userDropdown').classList.remove('active');
}
});
function selectUser(id, username) {
selectedUserId = id;
document.getElementById('addUserInput').value = username;
document.getElementById('userDropdown').classList.remove('active');
// Auto-add the user
const form = new FormData();
form.append('user_id', id);
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/add-user', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) {
showToast('success', username + ' added to team');
location.reload();
} else {
showToast('error', res.message);
document.getElementById('addUserInput').value = '';
}
});
}
/* ───── Funciones existentes ───── */
function removeMember(userId, username) {
if (!confirm('Remove "' + username + '" from this team?')) return;
const form = new FormData();
form.append('user_id', userId);
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/remove-user', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) { showToast('success', res.message); location.reload(); }
else { showToast('error', res.message); }
});
}
function addServer(e) {
e.preventDefault();
const serverId = document.getElementById('addServerId').value;
const role = document.getElementById('addServerRole').value;
if (!serverId) return;
const form = new FormData();
form.append('server_id', serverId);
form.append('role', role);
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/add-server', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) { showToast('success', res.message); location.reload(); }
else { showToast('error', res.message); }
});
}
function removeServer(serverId, name) {
if (!confirm('Remove "' + name + '" from this team?')) return;
const form = new FormData();
form.append('server_id', serverId);
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/remove-server', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) { showToast('success', res.message); location.reload(); }
else { showToast('error', res.message); }
});
}
function updateServerRole(serverId, role) {
const form = new FormData();
form.append('server_id', serverId);
form.append('role', role);
form.append('_csrf_token', getCsrfToken());
fetch('/teams/<?= $team['id'] ?>/update-server-role', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrfToken() },
body: form,
})
.then(r => r.json())
.then(res => {
if (res.success) { showToast('success', 'Role updated to ' + role); }
else { showToast('error', res.message); }
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
const teamId = <?= $team['id'] ?>;
const teamName = '<?= htmlspecialchars(addslashes($team['name'] ?? '')) ?>';
const teamDescription = '<?= htmlspecialchars(addslashes($team['description'] ?? '')) ?>';
</script>
<script src="/assets/js/team-show.js"></script>