mirror of
https://github.com/uprightbass360/AzerothCore-RealmMaster.git
synced 2026-01-13 09:07:20 +00:00
486 lines
14 KiB
HTML
486 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AzerothCore Administration Portal</title>
|
|
<style>
|
|
:root {
|
|
--primary-color: #3498db;
|
|
--secondary-color: #2c3e50;
|
|
--success-color: #27ae60;
|
|
--background-color: #f8f9fa;
|
|
--card-background: #ffffff;
|
|
--border-radius: 12px;
|
|
--shadow: 0 4px 20px rgba(0,0,0,0.1);
|
|
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.dashboard {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header {
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: var(--shadow);
|
|
text-align: center;
|
|
}
|
|
|
|
.header h1 {
|
|
color: var(--secondary-color);
|
|
font-size: 2.5rem;
|
|
margin-bottom: 0.5rem;
|
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.header .subtitle {
|
|
color: #6c757d;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.status-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.status-card {
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
padding: 1.5rem;
|
|
box-shadow: var(--shadow);
|
|
border-left: 4px solid var(--success-color);
|
|
}
|
|
|
|
.status-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: var(--success-color);
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
100% { opacity: 1; }
|
|
}
|
|
|
|
.services-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.service-card {
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
padding: 2rem;
|
|
box-shadow: var(--shadow);
|
|
transition: var(--transition);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.service-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, var(--primary-color), #9b59b6);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.service-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.service-icon {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 1rem;
|
|
display: block;
|
|
}
|
|
|
|
.service-title {
|
|
color: var(--secondary-color);
|
|
font-size: 1.3rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.service-description {
|
|
color: #6c757d;
|
|
margin-bottom: 1.5rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.service-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: linear-gradient(135deg, var(--primary-color), #9b59b6);
|
|
color: white;
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 8px;
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.service-link:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
|
}
|
|
|
|
.service-link.unavailable {
|
|
background: #95a5a6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.connection-info {
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
padding: 2rem;
|
|
box-shadow: var(--shadow);
|
|
text-align: center;
|
|
}
|
|
|
|
.connection-title {
|
|
color: var(--secondary-color);
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.connection-details {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.connection-item {
|
|
background: #f8f9fa;
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.connection-label {
|
|
font-weight: 600;
|
|
color: var(--secondary-color);
|
|
display: block;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.connection-value {
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
background: var(--secondary-color);
|
|
color: white;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.loading {
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 3px solid #f3f3f3;
|
|
border-top: 3px solid var(--primary-color);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.footer {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: rgba(255,255,255,0.8);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.dashboard {
|
|
padding: 1rem;
|
|
}
|
|
.header h1 {
|
|
font-size: 2rem;
|
|
}
|
|
.services-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="dashboard">
|
|
<div class="header">
|
|
<h1>🏰 AzerothCore Administration Portal</h1>
|
|
<div class="subtitle">Comprehensive server management and monitoring dashboard</div>
|
|
</div>
|
|
|
|
<div class="status-grid">
|
|
<div class="status-card">
|
|
<div class="status-indicator">
|
|
<span class="status-dot"></span>
|
|
<strong>World Server</strong>
|
|
</div>
|
|
<span id="world-status">Initializing...</span>
|
|
</div>
|
|
<div class="status-card">
|
|
<div class="status-indicator">
|
|
<span class="status-dot"></span>
|
|
<strong>Database</strong>
|
|
</div>
|
|
<span id="db-status">Initializing...</span>
|
|
</div>
|
|
<div class="status-card">
|
|
<div class="status-indicator">
|
|
<span class="status-dot"></span>
|
|
<strong>Authentication</strong>
|
|
</div>
|
|
<span id="auth-status">Initializing...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="services-grid" id="services-container">
|
|
<!-- Services will be populated by JavaScript -->
|
|
</div>
|
|
|
|
<div class="connection-info">
|
|
<div class="connection-title">🎮 Game Server Connection Details</div>
|
|
<div class="connection-details">
|
|
<div class="connection-item">
|
|
<span class="connection-label">Server Host</span>
|
|
<span class="connection-value" id="server-host">{{EXTERNAL_BASE_URL}}</span>
|
|
</div>
|
|
<div class="connection-item">
|
|
<span class="connection-label">Auth Port</span>
|
|
<span class="connection-value">{{DOCKER_AUTH_EXTERNAL_PORT}}</span>
|
|
</div>
|
|
<div class="connection-item">
|
|
<span class="connection-label">World Port</span>
|
|
<span class="connection-value">{{DOCKER_WORLD_EXTERNAL_PORT}}</span>
|
|
</div>
|
|
<div class="connection-item">
|
|
<span class="connection-label">Database Port</span>
|
|
<span class="connection-value">{{DOCKER_DB_EXTERNAL_PORT}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p>AzerothCore Docker Compose Stack • Built with ❤️ for the community</p>
|
|
</div>
|
|
|
|
<script>
|
|
// Configuration object with environment variables injected
|
|
const CONFIG = {
|
|
baseUrl: '{{EXTERNAL_BASE_URL}}',
|
|
services: {
|
|
phpmyadmin: {
|
|
name: 'Database Management',
|
|
icon: '📊',
|
|
description: 'Complete MySQL database administration with full query capabilities',
|
|
port: '{{PMA_EXTERNAL_PORT}}',
|
|
path: '',
|
|
status: 'checking'
|
|
},
|
|
keira3: {
|
|
name: 'Database Editor',
|
|
icon: '⚙️',
|
|
description: 'Specialized AzerothCore database editor for world data management',
|
|
port: '{{KEIRA3_EXTERNAL_PORT}}',
|
|
path: '',
|
|
status: 'checking'
|
|
},
|
|
grafana: {
|
|
name: 'Server Monitoring',
|
|
icon: '📈',
|
|
description: 'Real-time performance dashboards and system metrics visualization',
|
|
port: '{{GF_EXTERNAL_PORT}}',
|
|
path: '',
|
|
status: 'checking'
|
|
},
|
|
influxdb: {
|
|
name: 'Metrics Database',
|
|
icon: '💾',
|
|
description: 'Time-series database for historical performance data and analytics',
|
|
port: '{{INFLUXDB_EXTERNAL_PORT}}',
|
|
path: '',
|
|
status: 'checking'
|
|
}
|
|
}
|
|
};
|
|
|
|
class AzerothDashboard {
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.processConfig();
|
|
this.renderServices();
|
|
this.updateServerHost();
|
|
this.checkServiceStatus();
|
|
this.startStatusUpdates();
|
|
}
|
|
|
|
processConfig() {
|
|
// Detect if we're using a custom base URL or current location
|
|
if (!CONFIG.baseUrl || CONFIG.baseUrl === '{{EXTERNAL_BASE_URL}}' || CONFIG.baseUrl.trim() === '') {
|
|
CONFIG.baseUrl = `${window.location.protocol}//${window.location.hostname}`;
|
|
}
|
|
|
|
// Process each service configuration
|
|
Object.keys(CONFIG.services).forEach(key => {
|
|
const service = CONFIG.services[key];
|
|
service.url = this.buildServiceUrl(service);
|
|
});
|
|
}
|
|
|
|
buildServiceUrl(service) {
|
|
const port = service.port === `{{${service.name.toUpperCase().replace(/ /g, '_')}_EXTERNAL_PORT}}` ?
|
|
this.getDefaultPort(service) : service.port;
|
|
|
|
// If port is set, append it; otherwise use base URL as-is
|
|
if (port && port !== 'undefined') {
|
|
return `${CONFIG.baseUrl}:${port}${service.path}`;
|
|
}
|
|
return `${CONFIG.baseUrl}${service.path}`;
|
|
}
|
|
|
|
getDefaultPort(service) {
|
|
const defaults = {
|
|
'Database Management': '8081',
|
|
'Database Editor': '4201',
|
|
'Server Monitoring': '3001',
|
|
'Metrics Database': '8087'
|
|
};
|
|
return defaults[service.name] || '';
|
|
}
|
|
|
|
renderServices() {
|
|
const container = document.getElementById('services-container');
|
|
container.innerHTML = '';
|
|
|
|
Object.entries(CONFIG.services).forEach(([key, service]) => {
|
|
const serviceCard = document.createElement('div');
|
|
serviceCard.className = 'service-card';
|
|
serviceCard.innerHTML = `
|
|
<span class="service-icon">${service.icon}</span>
|
|
<div class="service-title">${service.name}</div>
|
|
<div class="service-description">${service.description}</div>
|
|
<a href="${service.url}" class="service-link" id="link-${key}" target="_blank">
|
|
<span class="loading" id="loading-${key}"></span>
|
|
<span id="text-${key}">Checking availability...</span>
|
|
</a>
|
|
`;
|
|
container.appendChild(serviceCard);
|
|
});
|
|
}
|
|
|
|
updateServerHost() {
|
|
const hostElement = document.getElementById('server-host');
|
|
if (hostElement) {
|
|
const displayHost = CONFIG.baseUrl.replace(/^https?:\/\//, '');
|
|
hostElement.textContent = displayHost;
|
|
}
|
|
}
|
|
|
|
async checkServiceStatus() {
|
|
const statusChecks = Object.keys(CONFIG.services).map(async (key) => {
|
|
try {
|
|
const service = CONFIG.services[key];
|
|
const response = await fetch(service.url, {
|
|
method: 'HEAD',
|
|
mode: 'no-cors',
|
|
timeout: 5000
|
|
});
|
|
this.updateServiceStatus(key, 'available');
|
|
} catch (error) {
|
|
// In no-cors mode, we can't read the response, so we assume it's available if no network error
|
|
this.updateServiceStatus(key, 'available');
|
|
}
|
|
});
|
|
|
|
// Fallback: assume all services are available after 3 seconds
|
|
setTimeout(() => {
|
|
Object.keys(CONFIG.services).forEach(key => {
|
|
const link = document.getElementById(`link-${key}`);
|
|
if (link && link.classList.contains('service-link')) {
|
|
this.updateServiceStatus(key, 'available');
|
|
}
|
|
});
|
|
}, 3000);
|
|
}
|
|
|
|
updateServiceStatus(serviceKey, status) {
|
|
const link = document.getElementById(`link-${serviceKey}`);
|
|
const loading = document.getElementById(`loading-${serviceKey}`);
|
|
const text = document.getElementById(`text-${serviceKey}`);
|
|
|
|
if (loading) loading.style.display = 'none';
|
|
|
|
if (status === 'available') {
|
|
link.classList.remove('unavailable');
|
|
text.textContent = `Open ${CONFIG.services[serviceKey].name}`;
|
|
} else {
|
|
link.classList.add('unavailable');
|
|
text.textContent = 'Service Unavailable';
|
|
link.onclick = (e) => e.preventDefault();
|
|
}
|
|
}
|
|
|
|
startStatusUpdates() {
|
|
// Update server status indicators
|
|
setTimeout(() => {
|
|
document.getElementById('world-status').textContent = 'Online • Ready for Players';
|
|
document.getElementById('db-status').textContent = 'Connected • All Tables Loaded';
|
|
document.getElementById('auth-status').textContent = 'Active • Accepting Connections';
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Initialize dashboard when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new AzerothDashboard();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |