498 lines
15 KiB
HTML
498 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AniWorld Manager - Initializing</title>
|
|
<link rel="stylesheet" href="/static/css/styles.css">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
.loading-container {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary) 100%);
|
|
padding: 2rem 1rem;
|
|
}
|
|
|
|
.loading-card {
|
|
background: var(--color-surface);
|
|
border-radius: 16px;
|
|
padding: 3rem 2rem;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
width: 100%;
|
|
max-width: 600px;
|
|
border: 1px solid var(--color-border);
|
|
}
|
|
|
|
.loading-header {
|
|
text-align: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.loading-header .logo {
|
|
font-size: 3.5rem;
|
|
color: var(--color-primary);
|
|
margin-bottom: 1rem;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: scale(1.05);
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
.loading-header h1 {
|
|
margin: 0;
|
|
color: var(--color-text);
|
|
font-size: 1.8rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.loading-header p {
|
|
margin: 0.5rem 0 0 0;
|
|
color: var(--color-text-secondary);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.progress-container {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.progress-step {
|
|
margin-bottom: 1.5rem;
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
background: var(--color-background);
|
|
border-left: 4px solid transparent;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.progress-step.active {
|
|
border-left-color: var(--color-primary);
|
|
background: var(--color-primary-light);
|
|
}
|
|
|
|
.progress-step.completed {
|
|
border-left-color: var(--color-success);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.progress-step.error {
|
|
border-left-color: var(--color-error);
|
|
}
|
|
|
|
.step-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.step-icon {
|
|
font-size: 1.2rem;
|
|
width: 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
.step-icon.loading {
|
|
color: var(--color-primary);
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.step-icon.completed {
|
|
color: var(--color-success);
|
|
}
|
|
|
|
.step-icon.error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
@keyframes spin {
|
|
from {
|
|
transform: rotate(0deg);
|
|
}
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.step-title {
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
font-size: 1rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.step-status {
|
|
font-size: 0.875rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.step-message {
|
|
font-size: 0.875rem;
|
|
color: var(--color-text-secondary);
|
|
margin-left: 2rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.step-progress {
|
|
margin-left: 2rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 6px;
|
|
background: var(--color-border);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
height: 100%;
|
|
background: var(--color-primary);
|
|
transition: width 0.3s ease;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.progress-text {
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-secondary);
|
|
margin-top: 0.25rem;
|
|
text-align: right;
|
|
}
|
|
|
|
.error-message {
|
|
background: var(--color-error-light);
|
|
border: 1px solid var(--color-error);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-top: 1rem;
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.error-message i {
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.completion-message {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.completion-message i {
|
|
font-size: 3rem;
|
|
color: var(--color-success);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.completion-message h2 {
|
|
margin: 0 0 0.5rem 0;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.completion-message p {
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.btn-continue {
|
|
background: var(--color-primary);
|
|
color: white;
|
|
border: none;
|
|
padding: 0.75rem 2rem;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-continue:hover {
|
|
background: var(--color-primary-dark);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.connection-status {
|
|
text-align: center;
|
|
padding: 1rem;
|
|
margin-top: 1rem;
|
|
font-size: 0.875rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.connection-status.connected {
|
|
color: var(--color-success);
|
|
}
|
|
|
|
.connection-status.disconnected {
|
|
color: var(--color-error);
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="loading-container">
|
|
<div class="loading-card">
|
|
<div class="loading-header">
|
|
<div class="logo">
|
|
<i class="fas fa-tv"></i>
|
|
</div>
|
|
<h1>Initializing AniWorld Manager</h1>
|
|
<p>Please wait while we set up your anime library...</p>
|
|
</div>
|
|
|
|
<div class="progress-container" id="progressContainer">
|
|
<!-- Steps will be dynamically added here -->
|
|
</div>
|
|
|
|
<div class="connection-status" id="connectionStatus">
|
|
<i class="fas fa-circle-notch fa-spin"></i> Connecting...
|
|
</div>
|
|
|
|
<div class="completion-message" id="completionMessage" style="display: none;">
|
|
<i class="fas fa-check-circle"></i>
|
|
<h2>Initialization Complete!</h2>
|
|
<p>Your anime library is ready to use.</p>
|
|
<button class="btn-continue" onclick="continueToApp()">
|
|
<i class="fas fa-arrow-right"></i> Continue to AniWorld Manager
|
|
</button>
|
|
</div>
|
|
|
|
<div class="error-message" id="errorMessage" style="display: none;">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<span id="errorText"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let ws = null;
|
|
const steps = new Map();
|
|
let isComplete = false;
|
|
|
|
const stepOrder = [
|
|
'series_sync',
|
|
'nfo_scan',
|
|
'media_scan'
|
|
];
|
|
|
|
const stepTitles = {
|
|
'series_sync': 'Syncing Series Database',
|
|
'nfo_scan': 'Processing NFO Metadata',
|
|
'media_scan': 'Scanning Media Files'
|
|
};
|
|
|
|
function connectWebSocket() {
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsUrl = `${protocol}//${window.location.host}/ws/connect`;
|
|
|
|
ws = new WebSocket(wsUrl);
|
|
|
|
ws.onopen = () => {
|
|
console.log('WebSocket connected');
|
|
updateConnectionStatus(true);
|
|
|
|
// Subscribe to system room for progress updates
|
|
ws.send(JSON.stringify({
|
|
action: 'join',
|
|
data: {
|
|
room: 'system'
|
|
}
|
|
}));
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
console.log('Progress update:', data);
|
|
handleProgressUpdate(data);
|
|
} catch (error) {
|
|
console.error('Error parsing WebSocket message:', error);
|
|
}
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
updateConnectionStatus(false);
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log('WebSocket disconnected');
|
|
updateConnectionStatus(false);
|
|
|
|
// Reconnect after delay if not complete
|
|
if (!isComplete) {
|
|
setTimeout(connectWebSocket, 3000);
|
|
}
|
|
};
|
|
}
|
|
|
|
function updateConnectionStatus(connected) {
|
|
const statusEl = document.getElementById('connectionStatus');
|
|
if (connected) {
|
|
statusEl.className = 'connection-status connected';
|
|
statusEl.innerHTML = '<i class="fas fa-check-circle"></i> Connected';
|
|
} else {
|
|
statusEl.className = 'connection-status disconnected';
|
|
statusEl.innerHTML = '<i class="fas fa-times-circle"></i> Disconnected - Reconnecting...';
|
|
}
|
|
}
|
|
|
|
function handleProgressUpdate(message) {
|
|
// Handle WebSocket message format: { type: string, data: {...} }
|
|
const data = message.data || message;
|
|
const { type, status, title, message: msg, percent, current, total, metadata } = data;
|
|
|
|
// Determine step ID based on type and metadata
|
|
let stepId = metadata?.step_id || type;
|
|
|
|
// Update or create step
|
|
if (!steps.has(stepId)) {
|
|
createStep(stepId, title || stepTitles[stepId] || stepId);
|
|
}
|
|
|
|
updateStep(stepId, status, msg, percent, current, total);
|
|
|
|
// Check for completion
|
|
if (metadata?.initialization_complete) {
|
|
showCompletion();
|
|
}
|
|
|
|
// Handle errors
|
|
if (status === 'failed') {
|
|
showError(msg || 'An error occurred during initialization');
|
|
}
|
|
}
|
|
|
|
function createStep(stepId, title) {
|
|
const container = document.getElementById('progressContainer');
|
|
|
|
const stepEl = document.createElement('div');
|
|
stepEl.id = `step-${stepId}`;
|
|
stepEl.className = 'progress-step';
|
|
stepEl.innerHTML = `
|
|
<div class="step-header">
|
|
<i class="fas fa-circle-notch fa-spin step-icon loading"></i>
|
|
<span class="step-title">${title}</span>
|
|
<span class="step-status">Waiting...</span>
|
|
</div>
|
|
<div class="step-message"></div>
|
|
<div class="step-progress" style="display: none;">
|
|
<div class="progress-bar">
|
|
<div class="progress-bar-fill" style="width: 0%;"></div>
|
|
</div>
|
|
<div class="progress-text">0%</div>
|
|
</div>
|
|
`;
|
|
|
|
// Insert in correct order
|
|
const existingSteps = Array.from(steps.keys());
|
|
let insertBefore = null;
|
|
|
|
for (const orderId of stepOrder) {
|
|
if (orderId === stepId) break;
|
|
if (!existingSteps.includes(orderId)) {
|
|
continue;
|
|
}
|
|
insertBefore = null;
|
|
}
|
|
|
|
for (const orderId of stepOrder.slice(stepOrder.indexOf(stepId) + 1)) {
|
|
const el = document.getElementById(`step-${orderId}`);
|
|
if (el) {
|
|
insertBefore = el;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (insertBefore) {
|
|
container.insertBefore(stepEl, insertBefore);
|
|
} else {
|
|
container.appendChild(stepEl);
|
|
}
|
|
|
|
steps.set(stepId, stepEl);
|
|
}
|
|
|
|
function updateStep(stepId, status, message, percent, current, total) {
|
|
const stepEl = steps.get(stepId);
|
|
if (!stepEl) return;
|
|
|
|
const iconEl = stepEl.querySelector('.step-icon');
|
|
const statusEl = stepEl.querySelector('.step-status');
|
|
const messageEl = stepEl.querySelector('.step-message');
|
|
const progressEl = stepEl.querySelector('.step-progress');
|
|
const progressFillEl = stepEl.querySelector('.progress-bar-fill');
|
|
const progressTextEl = stepEl.querySelector('.progress-text');
|
|
|
|
// Update status
|
|
stepEl.className = 'progress-step';
|
|
if (status === 'started' || status === 'in_progress') {
|
|
stepEl.classList.add('active');
|
|
iconEl.className = 'fas fa-circle-notch fa-spin step-icon loading';
|
|
statusEl.textContent = 'In Progress...';
|
|
} else if (status === 'completed') {
|
|
stepEl.classList.add('completed');
|
|
iconEl.className = 'fas fa-check-circle step-icon completed';
|
|
statusEl.textContent = 'Complete';
|
|
} else if (status === 'failed') {
|
|
stepEl.classList.add('error');
|
|
iconEl.className = 'fas fa-exclamation-circle step-icon error';
|
|
statusEl.textContent = 'Failed';
|
|
}
|
|
|
|
// Update message
|
|
if (message) {
|
|
messageEl.textContent = message;
|
|
messageEl.style.display = 'block';
|
|
}
|
|
|
|
// Update progress bar
|
|
if (percent > 0 || (current > 0 && total > 0)) {
|
|
const actualPercent = percent || (current / total * 100);
|
|
progressEl.style.display = 'block';
|
|
progressFillEl.style.width = `${actualPercent}%`;
|
|
progressTextEl.textContent = `${Math.round(actualPercent)}% (${current}/${total})`;
|
|
}
|
|
}
|
|
|
|
function showCompletion() {
|
|
isComplete = true;
|
|
document.getElementById('completionMessage').style.display = 'block';
|
|
document.getElementById('connectionStatus').style.display = 'none';
|
|
|
|
if (ws) {
|
|
ws.close();
|
|
}
|
|
}
|
|
|
|
function showError(message) {
|
|
const errorEl = document.getElementById('errorMessage');
|
|
const errorTextEl = document.getElementById('errorText');
|
|
errorTextEl.textContent = message;
|
|
errorEl.style.display = 'block';
|
|
}
|
|
|
|
function continueToApp() {
|
|
window.location.href = '/login';
|
|
}
|
|
|
|
// Start WebSocket connection when page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
connectWebSocket();
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|