Files
Aaron Kimbrell e3467465b4 Add dashboard audit log and configuration management
- Implemented dashboard audit logging with InsertAuditLog, GetRecentAuditLogs, GetAuditLogsByIP, and CleanupOldAuditLogs methods.
- Created dashboard configuration management with GetDashboardConfig and SetDashboardConfig methods.
- Added new tables for dashboard_audit_log and dashboard_config in both MySQL and SQLite migrations.
- Updated CMakeLists to include Crow and ASIO for dashboard server functionality.
- Enhanced existing database classes to support new dashboard features, including character, play key, and property management.
- Added new methods for retrieving and managing play keys, properties, and pet names.
- Updated TestSQLDatabase to include stubs for new dashboard-related methods.
- Modified shared and dashboard configuration files for new settings.
2026-04-22 11:01:41 -05:00

164 lines
6.5 KiB
HTML

<div class="row">
<div class="col-12">
<h1 class="mb-4">
<i class="bi bi-person-badge"></i>
Character Management
</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">All Characters</h5>
</div>
<div class="card-body">
<table id="characters-table" class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Account</th>
<th>Name</th>
<th>Pending Name</th>
<th>Needs Rename</th>
<th>Last Login</th>
<th>Permission Map</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Populated via DataTables Ajax -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// Wait for jQuery + DataTables to be available
function showLibraryError(message) {
const el = document.getElementById('characters-table');
if (el) {
const wrapper = document.createElement('div');
wrapper.className = 'alert alert-danger';
wrapper.textContent = message;
el.replaceWith(wrapper);
} else {
alert(message);
}
}
function loadCharacters() {
API.get('/api/auth/status').then(status => {
if (!status || !status.authenticated || status.gm_level < 3) {
showLibraryError('You do not have permission to view characters. Please log in with sufficient GM level.');
return;
}
API.get('/api/characters').then(res => {
const data = Array.isArray(res.data) ? res.data : (res || []);
if ($.fn.DataTable.isDataTable('#characters-table')) {
const table = $('#characters-table').DataTable();
table.clear();
table.rows.add(data);
table.draw(false);
} else {
const table = $('#characters-table').DataTable({
data: data,
columns: [
{ data: 'id' },
{ data: 'account_name', render: function(d, t, row) {
return row.account_id ? `<a href="/accounts/view/${row.account_id}">${d || row.account_id}</a>` : (d || '-');
}},
{ data: 'name', render: function(d, t, row) {
return `<a href="/characters/view/${row.id}">${d}</a>`;
}},
{ data: 'pending_name', render: d => d || '-' },
{ data: 'needs_rename', render: d => d ? '<span class="badge bg-warning">Yes</span>' : '<span class="badge bg-success">No</span>' },
{ data: 'last_login', render: function(d) {
if (!d || d === 0) return 'Never';
return new Date(d * 1000).toLocaleString();
}},
{ data: 'permission_map', render: d => d || '-' },
{ data: null, orderable: false, render: function(data, type, row) {
return `
<div class="btn-group btn-group-sm" role="group">
<a href="/characters/view/${row.id}" class="btn btn-info" title="View"><i class="bi bi-eye"></i></a>
<button data-char-id="${row.id}" class="btn btn-warning js-rescue-char" title="Rescue Character"><i class="bi bi-life-preserver"></i></button>
<button data-char-id="${row.id}" class="btn btn-danger js-delete-char" title="Delete Character"><i class="bi bi-trash"></i></button>
</div>`;
}}
],
pageLength: 25,
order: [[0, 'desc']],
processing: true
});
// Delegated event handlers
$('#characters-table').on('click', '.js-rescue-char', function() {
const id = $(this).data('char-id');
rescueCharacter(id, table);
});
$('#characters-table').on('click', '.js-delete-char', function() {
const id = $(this).data('char-id');
deleteCharacter(id, table);
});
}
}).catch(err => {
const msg = err && err.message ? err.message : 'Failed to load characters';
showLibraryError(`Error loading characters: ${msg}`);
});
}).catch(err => {
showLibraryError(`Error checking authentication: ${err && err.message ? err.message : err}`);
});
}
// Initialize when jQuery/DataTables and API are ready
safeInit(function($) {
loadCharacters();
}, { requireApi: true, timeout: 8000 });
async function rescueCharacter(charId, table) {
if (!confirm('Are you sure you want to rescue this character? This will move them to a safe location.')) return;
try {
const result = await API.post(`/api/characters/${charId}/rescue`);
if (result.success) {
showAlert('success', 'Character rescued successfully');
if (table && table.ajax) table.ajax.reload();
} else {
showAlert('danger', result.error || 'Failed to rescue character');
}
} catch (error) {
showAlert('danger', error.message || error);
}
}
async function deleteCharacter(charId, table) {
const confirmMsg = 'Are you sure you want to DELETE this character? This action is irreversible!';
if (!confirm(confirmMsg)) return;
const doubleConfirm = prompt('Type "DELETE" to confirm:');
if (doubleConfirm !== 'DELETE') {
showAlert('info', 'Deletion cancelled');
return;
}
try {
const result = await API.post(`/api/characters/${charId}/delete`);
if (result.success) {
showAlert('success', 'Character deleted');
if (table && table.ajax) table.ajax.reload();
} else {
showAlert('danger', result.error || 'Failed to delete character');
}
} catch (error) {
showAlert('danger', error.message || error);
}
}
</script>