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:
308
views/contentdb/installed.ejs
Normal file
308
views/contentdb/installed.ejs
Normal file
@@ -0,0 +1,308 @@
|
||||
<%
|
||||
const body = `
|
||||
<div class="page-header">
|
||||
<h2>📦 Installed Packages</h2>
|
||||
<p>Manage your installed mods, games, and texture packs</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>📊 Statistics</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="stat-item">
|
||||
<strong>${statistics.total_packages || 0}</strong>
|
||||
<span>Total Packages</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<strong>${statistics.global_packages || 0}</strong>
|
||||
<span>Global Mods</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<strong>${statistics.world_packages || 0}</strong>
|
||||
<span>World-specific</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<strong>${statistics.worlds_with_packages || 0}</strong>
|
||||
<span>Worlds with Mods</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>🔍 Filter Packages</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="/contentdb/installed"
|
||||
class="btn ${selectedLocation === 'all' ? 'btn-success' : 'btn-outline-secondary'} btn-sm btn-block">
|
||||
All Locations
|
||||
</a>
|
||||
<a href="/contentdb/installed?location=global"
|
||||
class="btn ${selectedLocation === 'global' ? 'btn-success' : 'btn-outline-secondary'} btn-sm btn-block">
|
||||
Global Mods
|
||||
</a>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">World-specific filters coming soon</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
${packages.length === 0 ? `
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h3>📭 No Packages Installed</h3>
|
||||
<p>You haven't installed any packages yet from ContentDB.</p>
|
||||
<a href="/contentdb" class="btn btn-primary">
|
||||
Browse ContentDB
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
<div class="packages-grid">
|
||||
${packages.map(pkg => `
|
||||
<div class="card package-card">
|
||||
<div class="card-header">
|
||||
<div class="package-title">
|
||||
<h4>${pkg.title || pkg.name}</h4>
|
||||
<small class="text-muted">by ${pkg.author}</small>
|
||||
</div>
|
||||
<div class="package-actions">
|
||||
<span class="badge badge-${pkg.package_type === 'game' ? 'success' : pkg.package_type === 'txp' ? 'warning' : 'primary'}">
|
||||
${pkg.package_type || 'mod'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="package-details">
|
||||
<p class="package-description">
|
||||
${pkg.short_description || 'No description available.'}
|
||||
</p>
|
||||
|
||||
<div class="package-meta">
|
||||
<div class="meta-item">
|
||||
<strong>Location:</strong>
|
||||
<span class="location-badge ${pkg.install_location === 'global' ? 'global' : 'world'}">
|
||||
${pkg.install_location === 'global' ? 'Global' : pkg.install_location.replace('world:', '')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<strong>Version:</strong>
|
||||
<span>${pkg.version || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<strong>Installed:</strong>
|
||||
<span>${new Date(pkg.installed_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${pkg.dependencies && pkg.dependencies.length > 0 ? `
|
||||
<div class="dependencies">
|
||||
<strong>Dependencies (${pkg.dependencies.length}):</strong>
|
||||
<div class="dep-list">
|
||||
${pkg.dependencies.map(dep =>
|
||||
typeof dep === 'string' ? dep : `${dep.author}/${dep.name}`
|
||||
).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="package-actions">
|
||||
${pkg.contentdb_url ? `
|
||||
<a href="\${pkg.contentdb_url}" target="_blank" class="btn btn-outline-primary btn-sm">
|
||||
View on ContentDB
|
||||
</a>
|
||||
` : ''}
|
||||
<button class="btn btn-outline-warning btn-sm"
|
||||
onclick="checkForUpdate('\${pkg.author}', '\${pkg.name}', '\${pkg.install_location}')">
|
||||
Check Update
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm"
|
||||
onclick="uninstallPackage('\${pkg.author}', '\${pkg.name}', '\${pkg.install_location}')">
|
||||
Uninstall
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.stat-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.stat-item strong {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.stat-item span {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-block), 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.package-title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.package-title h4 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.package-description {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
background: var(--bg-accent);
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.meta-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.location-badge.global {
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.location-badge.world {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dependencies {
|
||||
background: var(--bg-secondary);
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dep-list {
|
||||
margin-top: 0.5rem;
|
||||
color: var(--text-muted);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.package-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: var(--warning-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.package-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.package-actions .btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function checkForUpdate(author, name, location) {
|
||||
alert('Update checking feature coming soon!');
|
||||
// TODO: Implement update checking
|
||||
}
|
||||
|
||||
function uninstallPackage(author, name, location) {
|
||||
if (confirm('Are you sure you want to uninstall ' + name + '?')) {
|
||||
alert('Uninstall feature coming soon!');
|
||||
// TODO: Implement package uninstallation
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`;
|
||||
%>
|
||||
|
||||
<%- include('../layout', { body: body, currentPage: 'contentdb', title: title }) %>
|
Reference in New Issue
Block a user