Initial commit: LuHost - Luanti Server Management Web Interface
A modern web interface for Luanti (Minetest) server management with ContentDB integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
114
views/users/index.ejs
Normal file
114
views/users/index.ejs
Normal file
@@ -0,0 +1,114 @@
|
||||
<%
|
||||
const body = `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>User Management</h2>
|
||||
<a href="/users/new" class="btn btn-success">Create New User</a>
|
||||
</div>
|
||||
|
||||
${typeof req !== 'undefined' && req.query.created ? `
|
||||
<div class="alert alert-success">
|
||||
User "${req.query.created}" created successfully!
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query.deleted ? `
|
||||
<div class="alert alert-info">
|
||||
User deleted successfully.
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${typeof req !== 'undefined' && req.query.error ? `
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error:</strong> ${req.query.error}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Feudal Authority:</strong> Only you can create new user accounts. All users have full administrator privileges over the Luanti server.
|
||||
</div>
|
||||
|
||||
${users.length === 0 ? `
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">👥</div>
|
||||
<h3>No Users Found</h3>
|
||||
<p>This shouldn't happen since you're logged in. Please report this issue.</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Created</th>
|
||||
<th>Last Login</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${users.map(user => `
|
||||
<tr>
|
||||
<td>
|
||||
<strong>${user.username}</strong>
|
||||
${user.id === 1 ? '<span class="status" style="background: #e8f5e8; color: #2e7d32; margin-left: 0.5rem;">Founder</span>' : ''}
|
||||
</td>
|
||||
<td>
|
||||
<small>${formatDate(user.created_at)}</small>
|
||||
</td>
|
||||
<td>
|
||||
${user.last_login ? `<small>${formatDate(user.last_login)}</small>` : '<small style="color: var(--text-secondary);">Never</small>'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
${user.id !== 1 ? `
|
||||
<form method="POST" action="/users/delete/${user.id}"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirmDelete('user', '${user.username}')">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
` : `
|
||||
<span class="btn btn-sm btn-secondary" style="cursor: not-allowed;" title="Cannot delete founder account">Protected</span>
|
||||
`}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Authority & Permissions</h3>
|
||||
<div style="display: grid; gap: 1rem;">
|
||||
<div>
|
||||
<h4 style="color: var(--primary-color); margin-bottom: 0.5rem;">🏰 Feudal System</h4>
|
||||
<p>This server uses an "implicit feudalism" security model:</p>
|
||||
<ul style="margin-left: 1.5rem; color: var(--text-secondary);">
|
||||
<li>Only existing administrators can create new accounts</li>
|
||||
<li>The founder account (first user) cannot be deleted</li>
|
||||
<li>All users have equal administrative privileges</li>
|
||||
<li>No public registration - authority must be granted</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 style="color: var(--success-color); margin-bottom: 0.5rem;">👑 Administrative Powers</h4>
|
||||
<p>Every user account can:</p>
|
||||
<ul style="margin-left: 1.5rem; color: var(--text-secondary);">
|
||||
<li>Manage worlds (create, configure, delete)</li>
|
||||
<li>Install and manage mods</li>
|
||||
<li>Browse and install from ContentDB</li>
|
||||
<li>Control the Luanti server (start, stop, restart)</li>
|
||||
<li>Modify server configuration</li>
|
||||
<li>Create additional user accounts</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
%>
|
||||
|
||||
<%- include('../layout', { body: body, currentPage: 'users', title: title }) %>
|
106
views/users/new.ejs
Normal file
106
views/users/new.ejs
Normal file
@@ -0,0 +1,106 @@
|
||||
<%
|
||||
const body = `
|
||||
<div style="max-width: 500px; margin: 2rem auto;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Create New Administrator</h2>
|
||||
<p style="color: var(--text-secondary); margin: 0;">
|
||||
Grant administrative access to a new user
|
||||
</p>
|
||||
</div>
|
||||
|
||||
${typeof error !== 'undefined' ? `
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error:</strong> ${error}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Authority Note:</strong> This user will have full administrative privileges over the Luanti server, including the ability to create additional accounts.
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/users/create">
|
||||
${typeof csrfToken !== 'undefined' && csrfToken ? `<input type="hidden" name="_csrf" value="${csrfToken}">` : ''}
|
||||
<div class="form-group">
|
||||
<label for="username">Username*</label>
|
||||
<input type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
class="form-control"
|
||||
value="${typeof formData !== 'undefined' ? formData.username || '' : ''}"
|
||||
required
|
||||
pattern="[a-zA-Z0-9_-]{3,20}"
|
||||
title="3-20 characters, letters, numbers, underscore, or hyphen only"
|
||||
data-validate-name
|
||||
autofocus
|
||||
autocomplete="username">
|
||||
<small style="color: var(--text-secondary);">3-20 characters, letters, numbers, underscore, or hyphen only</small>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="password">Password*</label>
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
required
|
||||
minlength="8"
|
||||
autocomplete="new-password">
|
||||
<small style="color: var(--text-secondary);">At least 8 characters long</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">Confirm Password*</label>
|
||||
<input type="password"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
class="form-control"
|
||||
required
|
||||
minlength="8"
|
||||
autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2rem;">
|
||||
<a href="/users" class="btn btn-secondary">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
Create Administrator
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 1rem; color: var(--text-secondary); font-size: 0.875rem;">
|
||||
<p>This user will be able to perform all server management tasks and create additional accounts.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Client-side password confirmation validation
|
||||
document.getElementById('confirmPassword').addEventListener('input', function() {
|
||||
const password = document.getElementById('password').value;
|
||||
const confirmPassword = this.value;
|
||||
|
||||
if (password && confirmPassword) {
|
||||
if (password !== confirmPassword) {
|
||||
this.setCustomValidity('Passwords do not match');
|
||||
} else {
|
||||
this.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('password').addEventListener('input', function() {
|
||||
const confirmPassword = document.getElementById('confirmPassword');
|
||||
if (confirmPassword.value) {
|
||||
confirmPassword.dispatchEvent(new Event('input'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
`;
|
||||
%>
|
||||
|
||||
<%- include('../layout', { body: body, currentPage: 'users', title: title }) %>
|
Reference in New Issue
Block a user