625 lines
36 KiB
Plaintext
625 lines
36 KiB
Plaintext
---
|
|
// src/pages/ansible/sandbox.astro - Converted from static ansible-sandbox.html
|
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
|
import Header from '../../components/Header.astro';
|
|
import Footer from '../../components/Footer.astro';
|
|
|
|
const title = "Ansible Sandbox | Argobox";
|
|
const description = "Deploy interactive visualizations with ArgoBox Ansible Sandbox - the easiest way to deploy web animations and showcase your creative work.";
|
|
|
|
// Template data (can be externalized later)
|
|
const templates = [
|
|
{ id: "fireworks", name: "Fireworks", description: "Interactive fireworks animation with click-to-launch effects", icon: "fas fa-fire", tags: ["Popular"], complexity: "Basic" },
|
|
{ id: "matrix", name: "Matrix Rain", description: "Digital rain effect inspired by The Matrix movie", icon: "fas fa-code", tags: [], complexity: "Intermediate" },
|
|
{ id: "starfield", name: "Starfield", description: "3D space journey through a field of stars", icon: "fas fa-star", tags: [], complexity: "Basic" },
|
|
{ id: "particles", name: "Particles", description: "Interactive particle system that responds to mouse movement", icon: "fas fa-atom", tags: [], complexity: "Intermediate" },
|
|
{ id: "3d-globe", name: "3D Globe", description: "Interactive 3D Earth globe with custom markers", icon: "fas fa-globe-americas", tags: ["New"], complexity: "Advanced" }
|
|
];
|
|
---
|
|
|
|
<BaseLayout {title} {description}>
|
|
{/* Add Font Awesome if not loaded globally */}
|
|
{/* <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" /> */}
|
|
<Header slot="header" />
|
|
|
|
<main class="container sandbox-page-container"> {/* Use specific container class */}
|
|
|
|
<!-- Sandbox Container -->
|
|
<div class="sandbox-container card"> {/* Re-use card style for main container */}
|
|
|
|
<!-- Simulation Toggle -->
|
|
<div class="simulation-toggle">
|
|
<span class="toggle-label">Simulation Mode</span>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="simulation-toggle" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<span class="toggle-status">Active</span>
|
|
</div>
|
|
|
|
<!-- Offline Notice -->
|
|
<div class="offline-notice">
|
|
<div class="offline-notice-icon">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</div>
|
|
<div class="offline-notice-text">
|
|
<h3>Ansible Sandbox is Currently Offline</h3>
|
|
<p>The Ansible Sandbox environment is currently in simulation mode. You can explore the interface, but actual deployments are not available at this time. We're working to bring the full functionality online soon.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sandbox Header -->
|
|
<div class="sandbox-header">
|
|
<div class="sandbox-title">
|
|
<h1>Template Deployment</h1>
|
|
</div>
|
|
<div class="sandbox-actions">
|
|
<a href="/ansible/help" class="sandbox-btn"> {/* Updated Link */}
|
|
<i class="fas fa-question-circle"></i>
|
|
<span>Help</span>
|
|
</a>
|
|
<a href="/ansible/docs" class="sandbox-btn"> {/* Updated Link */}
|
|
<i class="fas fa-book"></i>
|
|
<span>Documentation</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Template Selection Section -->
|
|
<div class="templates-section">
|
|
<h2 class="section-title">Select a Template to Deploy</h2>
|
|
<p class="section-description">Choose from our collection of interactive visualizations to deploy with Ansible automation.</p>
|
|
|
|
<div class="templates-grid">
|
|
{templates.map(template => (
|
|
<div class="template-card" data-template={template.id}>
|
|
<div class="template-preview">
|
|
<i class={template.icon}></i>
|
|
</div>
|
|
<div class="template-content">
|
|
<h3 class="template-name">{template.name}</h3>
|
|
<p class="template-description">{template.description}</p>
|
|
<div class="template-meta">
|
|
{template.tags.map(tag => <span class="template-tag">{tag}</span>)}
|
|
<span class="template-complexity">{template.complexity}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div class="deployment-actions">
|
|
<button id="preview-btn" class="sandbox-btn" disabled>
|
|
<i class="fas fa-eye"></i>
|
|
<span>Preview Template</span>
|
|
</button>
|
|
<button id="deploy-btn" class="sandbox-btn btn-primary" disabled>
|
|
<i class="fas fa-rocket"></i>
|
|
<span>Deploy Now</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Deployment Status Section (Initially Hidden) -->
|
|
<div id="deployment-section" class="deployment-section" style="display: none;">
|
|
<h2 class="section-title">Deployment Progress</h2>
|
|
<div class="status-container">
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar">
|
|
<div class="progress-value" id="deployment-progress"></div>
|
|
</div>
|
|
<div class="progress-label" id="progress-text">Initializing deployment...</div>
|
|
</div>
|
|
<div class="deployment-steps">
|
|
<div class="deployment-step pending" id="step-init">
|
|
<div class="step-icon">1</div>
|
|
<div class="step-content">
|
|
<div class="step-title">Initialization</div>
|
|
<div class="step-description">Setting up deployment environment</div>
|
|
</div>
|
|
</div>
|
|
<div class="deployment-step pending" id="step-template">
|
|
<div class="step-icon">2</div>
|
|
<div class="step-content">
|
|
<div class="step-title">Template Preparation</div>
|
|
<div class="step-description">Configuring selected template</div>
|
|
</div>
|
|
</div>
|
|
<div class="deployment-step pending" id="step-deploy">
|
|
<div class="step-icon">3</div>
|
|
<div class="step-content">
|
|
<div class="step-title">Deployment</div>
|
|
<div class="step-description">Executing Ansible deployment</div>
|
|
</div>
|
|
</div>
|
|
<div class="deployment-step pending" id="step-publish">
|
|
<div class="step-icon">4</div>
|
|
<div class="step-content">
|
|
<div class="step-title">Publishing</div>
|
|
<div class="step-description">Making your site live</div> {/* Simplified */}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="deployment-details">
|
|
<div class="deployment-info" id="deployment-info">
|
|
<p>Deployment information will appear here once the process begins.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Deployment Result Section (Initially Hidden) -->
|
|
<div id="result-section" class="result-section" style="display: none;">
|
|
<div class="success-card">
|
|
<div class="success-icon">
|
|
<i class="fas fa-check-circle"></i>
|
|
</div>
|
|
<h2>Deployment Successful!</h2>
|
|
<p>Your template has been deployed and is now live.</p>
|
|
<div class="deployment-url-container">
|
|
<p>You can access your deployment at:</p>
|
|
<a href="#" class="deployment-url" id="deployment-url" target="_blank" rel="noopener noreferrer">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
<span id="deployment-url-text">https://u12345.argobox.com</span>
|
|
</a>
|
|
</div>
|
|
<div class="result-actions">
|
|
<button id="view-deployment-btn" class="sandbox-btn btn-primary">
|
|
<i class="fas fa-eye"></i>
|
|
<span>View Deployment</span>
|
|
</button>
|
|
<button id="new-deployment-btn" class="sandbox-btn">
|
|
<i class="fas fa-plus"></i>
|
|
<span>New Deployment</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sandbox Footer -->
|
|
<div class="sandbox-footer">
|
|
<a href="/" class="back-to-site"> {/* Updated Link */}
|
|
<i class="fas fa-arrow-left"></i>
|
|
<span>Back to Main Site</span>
|
|
</a>
|
|
<div class="footer-info">Template deployments automatically expire after 30 minutes.</div> {/* Adjusted expiry */}
|
|
</div>
|
|
|
|
</div> {/* End sandbox-container */}
|
|
</main>
|
|
|
|
<Footer slot="footer" />
|
|
</BaseLayout>
|
|
|
|
<style is:global>
|
|
/* Styles adapted from ansible-sandbox.html and styles.css */
|
|
/* Use theme variables where possible */
|
|
|
|
.sandbox-page-container {
|
|
padding-top: 2rem; /* Add padding above main card */
|
|
padding-bottom: 4rem;
|
|
}
|
|
|
|
.card { /* Style for the main sandbox container */
|
|
background-color: var(--card-bg);
|
|
border-radius: 0.75rem; /* Slightly larger radius */
|
|
box-shadow: var(--card-shadow);
|
|
padding: clamp(1.5rem, 5vw, 2.5rem); /* Responsive padding */
|
|
margin-bottom: 2rem;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
/* Simulation Toggle Styles */
|
|
.simulation-toggle { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; background-color: var(--bg-secondary); padding: 0.75rem 1rem; border-radius: 0.375rem; border: 1px solid var(--border); width: fit-content; }
|
|
.toggle-label { font-size: 0.875rem; font-weight: 500; color: var(--text-secondary); }
|
|
.toggle-switch { position: relative; display: inline-block; width: 40px; height: 20px; }
|
|
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
|
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--text-secondary); transition: .4s; border-radius: 20px; }
|
|
.toggle-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; }
|
|
input:checked + .toggle-slider { background-color: var(--accent); }
|
|
input:checked + .toggle-slider:before { transform: translateX(20px); }
|
|
.toggle-status { font-size: 0.875rem; font-weight: 500; color: var(--text-primary); }
|
|
|
|
/* Offline Notice Styles */
|
|
.offline-notice { display: flex; align-items: center; background-color: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); border-left: 4px solid var(--warning); padding: 1rem 1.5rem; border-radius: 0.5rem; margin-bottom: 2rem; }
|
|
.offline-notice-icon { font-size: 1.5rem; color: var(--warning); margin-right: 1rem; }
|
|
.offline-notice-text h3 { font-size: 1.1rem; font-weight: 600; color: var(--text-primary); margin-bottom: 0.25rem; }
|
|
.offline-notice-text p { font-size: 0.9rem; color: var(--text-secondary); margin: 0; }
|
|
|
|
/* Sandbox Header Styles */
|
|
.sandbox-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border); flex-wrap: wrap; gap: 1rem; /* Allow wrapping */ }
|
|
.sandbox-title h1 { font-size: clamp(1.5rem, 4vw, 1.8rem); margin: 0; color: var(--text-primary); }
|
|
.sandbox-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
|
|
.sandbox-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background-color: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 0.375rem; text-decoration: none; font-size: 0.875rem; font-weight: 500; transition: all var(--transition-normal); cursor: pointer; }
|
|
.sandbox-btn:hover { background-color: var(--bg-tertiary); color: var(--text-primary); border-color: var(--text-secondary); }
|
|
.sandbox-btn.btn-primary { background-color: var(--accent); color: white; border-color: var(--accent); }
|
|
.sandbox-btn.btn-primary:hover { background-color: var(--accent-darker); border-color: var(--accent-darker); }
|
|
.sandbox-btn:disabled { opacity: 0.6; cursor: not-allowed; background-color: var(--bg-secondary); color: var(--text-secondary); border-color: var(--border); }
|
|
.sandbox-btn.btn-primary:disabled { background-color: var(--accent); opacity: 0.5; border-color: var(--accent); }
|
|
|
|
/* Template Deployment Styles */
|
|
.section-title { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary); }
|
|
.section-description { font-size: 0.95rem; color: var(--text-secondary); margin-bottom: 2rem; }
|
|
.templates-section { margin-bottom: 3rem; }
|
|
.templates-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } /* Slightly smaller minmax */
|
|
.template-card { background-color: var(--card-bg); border: 1px solid var(--border); border-radius: 0.75rem; overflow: hidden; cursor: pointer; transition: all var(--transition-normal); display: flex; flex-direction: column; }
|
|
.template-card:hover { transform: translateY(-5px); box-shadow: var(--card-shadow); border-color: var(--accent); }
|
|
.template-card.selected { border: 2px solid var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--card-shadow); background-color: rgba(59, 130, 246, 0.05); }
|
|
.template-preview { height: 140px; background-color: var(--bg-secondary); display: flex; align-items: center; justify-content: center; font-size: 3rem; color: var(--accent); overflow: hidden; position: relative; border-bottom: 1px solid var(--border); }
|
|
.template-preview img { width: 100%; height: 100%; object-fit: cover; }
|
|
.template-content { padding: 1.25rem; flex: 1; display: flex; flex-direction: column; }
|
|
.template-name { font-size: 1.2rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary); }
|
|
.template-description { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 1rem; flex: 1; }
|
|
.template-meta { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; margin-top: auto; } /* Push meta to bottom */
|
|
.template-tag { background-color: rgba(59, 130, 246, 0.1); color: var(--accent); padding: 0.25rem 0.75rem; border-radius: 9999px; font-weight: 500; }
|
|
.template-complexity { color: var(--text-secondary); font-weight: 500; }
|
|
.deployment-actions { display: flex; justify-content: flex-end; gap: 1rem; margin-top: 1rem; flex-wrap: wrap; }
|
|
|
|
/* Deployment Status Styles */
|
|
.deployment-section { margin-bottom: 3rem; }
|
|
.status-container { background-color: var(--card-bg); border: 1px solid var(--border); border-radius: 0.75rem; padding: 1.5rem; }
|
|
.progress-bar-container { margin-bottom: 2rem; }
|
|
.progress-bar { height: 0.5rem; background-color: var(--bg-tertiary); border-radius: 9999px; overflow: hidden; margin-bottom: 1rem; }
|
|
.progress-value { height: 100%; background-color: var(--accent); border-radius: 9999px; width: 0%; transition: width 0.5s ease; }
|
|
.progress-label { font-size: 0.9rem; color: var(--text-secondary); text-align: center; }
|
|
.deployment-steps { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem; }
|
|
.deployment-step { display: flex; align-items: flex-start; padding: 1rem; background-color: var(--bg-secondary); border-radius: 0.5rem; border-left: 4px solid var(--border); transition: all var(--transition-normal); }
|
|
.deployment-step.pending { border-left-color: var(--border); }
|
|
.deployment-step.in-progress { border-left-color: var(--accent); background-color: rgba(59, 130, 246, 0.05); }
|
|
.deployment-step.completed { border-left-color: var(--success); background-color: rgba(16, 185, 129, 0.05); }
|
|
.deployment-step.failed { border-left-color: var(--error); background-color: rgba(239, 68, 68, 0.05); }
|
|
.step-icon { width: 2rem; height: 2rem; border-radius: 50%; background-color: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 0.9rem; margin-right: 1rem; flex-shrink: 0; transition: all var(--transition-normal); color: var(--text-secondary); }
|
|
.pending .step-icon { background-color: var(--bg-tertiary); }
|
|
.in-progress .step-icon { background-color: var(--accent); color: white; }
|
|
.completed .step-icon { background-color: var(--success); color: white; }
|
|
.failed .step-icon { background-color: var(--error); color: white; }
|
|
.step-content { flex: 1; }
|
|
.step-title { font-weight: 600; font-size: 1rem; margin-bottom: 0.25rem; color: var(--text-primary); }
|
|
.step-description { font-size: 0.85rem; color: var(--text-secondary); }
|
|
.deployment-details { background-color: var(--bg-secondary); border-radius: 0.5rem; padding: 1.25rem; border: 1px solid var(--border); }
|
|
.deployment-info { font-size: 0.9rem; color: var(--text-secondary); }
|
|
.deployment-info dl { display: grid; grid-template-columns: auto 1fr; gap: 0.5rem 1rem; margin: 0; }
|
|
.deployment-info dt { font-weight: 600; color: var(--text-primary); grid-column: 1; text-align: right; }
|
|
.deployment-info dd { margin: 0; color: var(--text-secondary); grid-column: 2; word-break: break-all; }
|
|
.deployment-info .status-badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; font-weight: 600; background-color: var(--bg-tertiary); }
|
|
.deployment-info .status-initializing, .deployment-info .status-unknown { color: var(--info); }
|
|
.deployment-info .status-deploying, .deployment-info .status-publishing { color: var(--warning); }
|
|
.deployment-info .status-completed { color: var(--success); }
|
|
.deployment-info .status-failed { color: var(--error); }
|
|
.deployment-info dd a { color: var(--accent); text-decoration: none; }
|
|
.deployment-info dd a:hover { text-decoration: underline; }
|
|
|
|
/* Deployment Result Styles */
|
|
.result-section { margin-bottom: 3rem; }
|
|
.success-card { background-color: var(--card-bg); border: 1px solid var(--border); border-radius: 0.75rem; padding: 2.5rem 1.5rem; text-align: center; }
|
|
.success-icon { font-size: 4rem; color: var(--success); margin-bottom: 1.5rem; }
|
|
.success-card h2 { font-size: 1.75rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--text-primary); }
|
|
.success-card p { font-size: 1rem; color: var(--text-secondary); margin-bottom: 2rem; }
|
|
.deployment-url-container { margin-bottom: 2rem; }
|
|
.deployment-url-container p { margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-secondary); }
|
|
.deployment-url { display: inline-flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1.5rem; background-color: var(--bg-secondary); border: 1px solid var(--border); border-radius: 0.5rem; color: var(--accent); font-size: 1.1rem; font-weight: 500; text-decoration: none; transition: all var(--transition-normal); }
|
|
.deployment-url:hover { background-color: var(--bg-tertiary); transform: translateY(-2px); border-color: var(--accent); }
|
|
.result-actions { display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; }
|
|
|
|
/* Sandbox Footer Styles */
|
|
.sandbox-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); font-size: 0.85rem; color: var(--text-secondary); flex-wrap: wrap; gap: 1rem; }
|
|
.back-to-site { display: inline-flex; align-items: center; gap: 0.5rem; color: var(--text-secondary); text-decoration: none; transition: color var(--transition-normal); }
|
|
.back-to-site:hover { color: var(--accent); }
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.sandbox-header { flex-direction: column; align-items: flex-start; }
|
|
.sandbox-actions { width: 100%; }
|
|
.templates-grid { grid-template-columns: 1fr; }
|
|
.deployment-actions { flex-direction: column; }
|
|
.deployment-actions button { width: 100%; }
|
|
.result-actions { flex-direction: column; }
|
|
.result-actions button { width: 100%; }
|
|
.sandbox-footer { flex-direction: column; text-align: center; }
|
|
.deployment-info dl { grid-template-columns: 1fr; }
|
|
.deployment-info dt { text-align: left; margin-bottom: 0.1rem; }
|
|
.deployment-info dd { margin-bottom: 0.5rem; }
|
|
}
|
|
|
|
</style>
|
|
|
|
<script is:inline>
|
|
// Adapted Sandbox JS from ansible-sandbox.html
|
|
// Needs to run inline because it manipulates the DOM of this specific page
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Configuration
|
|
// Use Astro environment variables (import.meta.env)
|
|
// Make sure these are prefixed with PUBLIC_ if they need to be client-accessible
|
|
const API_BASE_URL = import.meta.env.PUBLIC_ANSIBLE_SANDBOX_API_URL || 'https://ansible-sandbox.fly.dev';
|
|
let SIMULATION_MODE = true; // Default to true, controlled by toggle
|
|
const BASE_DOMAIN = import.meta.env.PUBLIC_ANSIBLE_SANDBOX_DOMAIN || 'argobox.com';
|
|
|
|
// Elements
|
|
const templateCards = document.querySelectorAll('.template-card');
|
|
const deployBtn = document.getElementById('deploy-btn');
|
|
const previewBtn = document.getElementById('preview-btn');
|
|
const deploymentSection = document.getElementById('deployment-section');
|
|
const resultSection = document.getElementById('result-section');
|
|
const deploymentProgress = document.getElementById('deployment-progress');
|
|
const progressText = document.getElementById('progress-text');
|
|
const deploymentInfo = document.getElementById('deployment-info');
|
|
const deploymentUrl = document.getElementById('deployment-url');
|
|
const deploymentUrlText = document.getElementById('deployment-url-text');
|
|
const viewDeploymentBtn = document.getElementById('view-deployment-btn');
|
|
const newDeploymentBtn = document.getElementById('new-deployment-btn');
|
|
const simulationToggle = document.getElementById('simulation-toggle');
|
|
const toggleStatus = document.querySelector('.toggle-status');
|
|
const templatesSection = document.querySelector('.templates-section');
|
|
|
|
// State
|
|
let selectedTemplate = null;
|
|
let deploymentId = null;
|
|
let deploymentStatus = 'pending';
|
|
let pollingIntervalId = null;
|
|
|
|
// Initialize UI
|
|
function initUI() {
|
|
templateCards.forEach(card => card.addEventListener('click', () => selectTemplate(card)));
|
|
if (deployBtn) deployBtn.addEventListener('click', startDeployment);
|
|
if (previewBtn) previewBtn.addEventListener('click', previewTemplate);
|
|
if (viewDeploymentBtn) viewDeploymentBtn.addEventListener('click', () => {
|
|
if (deploymentUrl && deploymentUrl.href && deploymentUrl.href !== '#') {
|
|
window.open(deploymentUrl.href, '_blank');
|
|
}
|
|
});
|
|
if (newDeploymentBtn) newDeploymentBtn.addEventListener('click', resetDeployment);
|
|
|
|
if (simulationToggle) {
|
|
SIMULATION_MODE = simulationToggle.checked;
|
|
updateToggleStatus();
|
|
simulationToggle.addEventListener('change', () => {
|
|
SIMULATION_MODE = simulationToggle.checked;
|
|
updateToggleStatus();
|
|
console.log(`Simulation mode: ${SIMULATION_MODE}`);
|
|
});
|
|
}
|
|
|
|
// Check URL parameters
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const templateParam = urlParams.get('template');
|
|
if (templateParam) {
|
|
const matchingCard = Array.from(templateCards).find(card => card.dataset.template === templateParam);
|
|
if (matchingCard) {
|
|
selectTemplate(matchingCard);
|
|
if (urlParams.get('deploy') === 'true') {
|
|
setTimeout(startDeployment, 500);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateToggleStatus() {
|
|
if (toggleStatus) {
|
|
toggleStatus.textContent = SIMULATION_MODE ? 'Active' : 'Inactive';
|
|
}
|
|
}
|
|
|
|
function selectTemplate(card) {
|
|
templateCards.forEach(c => c.classList.remove('selected'));
|
|
card.classList.add('selected');
|
|
selectedTemplate = card.dataset.template;
|
|
if (deployBtn) deployBtn.disabled = false;
|
|
if (previewBtn) previewBtn.disabled = false;
|
|
}
|
|
|
|
function previewTemplate() {
|
|
if (!selectedTemplate) return;
|
|
alert(`Previewing ${selectedTemplate}... (Replace with actual preview logic)`);
|
|
}
|
|
|
|
function startDeployment() {
|
|
if (!selectedTemplate) return;
|
|
|
|
if (templatesSection) templatesSection.style.display = 'none';
|
|
if (deploymentSection) deploymentSection.style.display = 'block';
|
|
if (resultSection) resultSection.style.display = 'none';
|
|
|
|
resetProgressSteps();
|
|
updateProgress(5, 'Initializing deployment...');
|
|
updateStepStatus('step-init', 'in-progress');
|
|
if (deploymentInfo) deploymentInfo.innerHTML = '<p>Preparing deployment environment...</p>';
|
|
|
|
if (deployBtn) deployBtn.disabled = true;
|
|
if (previewBtn) previewBtn.disabled = true;
|
|
|
|
if (SIMULATION_MODE) {
|
|
simulateDeployment();
|
|
} else {
|
|
makeDeploymentRequest();
|
|
}
|
|
}
|
|
|
|
function makeDeploymentRequest() {
|
|
const payload = { template_name: selectedTemplate };
|
|
|
|
fetch(`${API_BASE_URL}/deploy`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
return response.json().then(err => { throw new Error(err.detail || `Deployment failed: ${response.statusText}`); })
|
|
.catch(() => { throw new Error(`Deployment failed: ${response.statusText}`); });
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
deploymentId = data.deployment_id;
|
|
console.log('Deployment initiated:', deploymentId);
|
|
const initialStatus = { deployment_id: deploymentId, template: selectedTemplate, status: 'initializing', timestamp: new Date().toISOString(), ...data };
|
|
updateDeploymentStatus(initialStatus);
|
|
pollDeploymentStatus();
|
|
})
|
|
.catch(error => {
|
|
console.error('Deployment error:', error);
|
|
updateProgress(0, `Error: ${error.message}`);
|
|
updateStepStatus('step-init', 'failed');
|
|
if (deploymentInfo) deploymentInfo.innerHTML = `<p style="color: var(--error);"><strong>Error:</strong> ${error.message}</p>`;
|
|
if (deployBtn) deployBtn.disabled = false;
|
|
if (previewBtn) previewBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
function pollDeploymentStatus() {
|
|
if (!deploymentId) return;
|
|
if (pollingIntervalId) clearInterval(pollingIntervalId);
|
|
|
|
pollingIntervalId = setInterval(() => {
|
|
fetch(`${API_BASE_URL}/status/${deploymentId}`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
if (response.status === 404) { console.warn(`Deployment ${deploymentId} not found yet.`); return null; }
|
|
throw new Error(`Status check failed: ${response.statusText}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data === null) return;
|
|
updateDeploymentStatus(data);
|
|
if (data.status === 'completed' || data.status === 'failed') {
|
|
clearInterval(pollingIntervalId);
|
|
pollingIntervalId = null;
|
|
if (deployBtn) deployBtn.disabled = false;
|
|
if (previewBtn) previewBtn.disabled = false;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Status check error:', error);
|
|
clearInterval(pollingIntervalId);
|
|
pollingIntervalId = null;
|
|
if (deploymentInfo) deploymentInfo.innerHTML += `<p style="color: var(--error);">Status polling failed: ${error.message}</p>`;
|
|
if (deployBtn) deployBtn.disabled = false;
|
|
if (previewBtn) previewBtn.disabled = false;
|
|
});
|
|
}, 3000);
|
|
}
|
|
|
|
function updateDeploymentStatus(data) {
|
|
deploymentStatus = data.status;
|
|
let progressPercent = 0;
|
|
let progressMessage = '';
|
|
resetProgressStepsVisually();
|
|
|
|
switch (deploymentStatus) {
|
|
case 'initializing':
|
|
progressPercent = 20; progressMessage = 'Initializing deployment...';
|
|
updateStepStatus('step-init', 'in-progress'); break;
|
|
case 'preparing': case 'deploying':
|
|
progressPercent = 40; progressMessage = 'Deploying template...';
|
|
updateStepStatus('step-init', 'completed'); updateStepStatus('step-template', 'completed'); updateStepStatus('step-deploy', 'in-progress'); break;
|
|
case 'publishing':
|
|
progressPercent = 70; progressMessage = 'Publishing...';
|
|
updateStepStatus('step-init', 'completed'); updateStepStatus('step-template', 'completed'); updateStepStatus('step-deploy', 'completed'); updateStepStatus('step-publish', 'in-progress'); break;
|
|
case 'completed':
|
|
progressPercent = 100; progressMessage = 'Deployment complete!';
|
|
updateStepStatus('step-init', 'completed'); updateStepStatus('step-template', 'completed'); updateStepStatus('step-deploy', 'completed'); updateStepStatus('step-publish', 'completed');
|
|
showDeploymentResult(data); break;
|
|
case 'failed':
|
|
progressPercent = 0; progressMessage = `Deployment failed: ${data.error || 'Unknown error'}`;
|
|
updateStepStatus('step-init', 'completed'); // Assume init ok
|
|
if (data.failed_step === 'template' || data.failed_step === 'deploy' || data.failed_step === 'publish') updateStepStatus('step-template', 'completed');
|
|
if (data.failed_step === 'deploy' || data.failed_step === 'publish') updateStepStatus('step-deploy', 'completed');
|
|
// Mark specific failed step
|
|
if (data.failed_step === 'init') updateStepStatus('step-init', 'failed');
|
|
else if (data.failed_step === 'template') updateStepStatus('step-template', 'failed');
|
|
else if (data.failed_step === 'deploy') updateStepStatus('step-deploy', 'failed');
|
|
else if (data.failed_step === 'publish') updateStepStatus('step-publish', 'failed');
|
|
else { // Fallback
|
|
const currentStep = document.querySelector('.deployment-step.in-progress');
|
|
if (currentStep) { currentStep.classList.remove('in-progress'); currentStep.classList.add('failed'); }
|
|
else { updateStepStatus('step-init', 'failed'); }
|
|
}
|
|
break;
|
|
default: console.warn("Unknown status:", deploymentStatus); progressMessage = `Status: ${deploymentStatus}`; break;
|
|
}
|
|
updateProgress(progressPercent, progressMessage);
|
|
updateDeploymentInfo(data);
|
|
}
|
|
|
|
function simulateDeployment() {
|
|
deploymentId = `sim-${Math.random().toString(36).substring(2, 9)}`;
|
|
selectedTemplate = selectedTemplate || 'fireworks';
|
|
const steps = [ { status: 'initializing', delay: 1500 }, { status: 'deploying', delay: 2500 }, { status: 'publishing', delay: 3000 }, { status: 'completed', delay: 1000 } ];
|
|
let currentDelay = 0;
|
|
steps.forEach(step => {
|
|
currentDelay += step.delay;
|
|
setTimeout(() => {
|
|
const statusData = { deployment_id: deploymentId, template: selectedTemplate, status: step.status, timestamp: new Date().toISOString() };
|
|
updateDeploymentStatus(statusData);
|
|
if (step.status === 'completed' || step.status === 'failed') {
|
|
if (pollingIntervalId) clearInterval(pollingIntervalId); pollingIntervalId = null;
|
|
if (deployBtn) deployBtn.disabled = false; if (previewBtn) previewBtn.disabled = false;
|
|
}
|
|
}, currentDelay);
|
|
});
|
|
}
|
|
|
|
function showDeploymentResult(data) {
|
|
const url = data.deployment_url || `https://${data.deployment_id}.${BASE_DOMAIN}`;
|
|
if (deploymentUrl) deploymentUrl.href = url;
|
|
if (deploymentUrlText) deploymentUrlText.textContent = url.replace(/^https?:\/\//, '');
|
|
if (deploymentSection) deploymentSection.style.display = 'none';
|
|
if (resultSection) resultSection.style.display = 'block';
|
|
}
|
|
|
|
function resetDeployment() {
|
|
if (pollingIntervalId) { clearInterval(pollingIntervalId); pollingIntervalId = null; }
|
|
if (resultSection) resultSection.style.display = 'none';
|
|
if (deploymentSection) deploymentSection.style.display = 'none';
|
|
if (templatesSection) templatesSection.style.display = 'block';
|
|
templateCards.forEach(card => card.classList.remove('selected'));
|
|
selectedTemplate = null; deploymentId = null; deploymentStatus = 'pending';
|
|
if (deployBtn) deployBtn.disabled = true; if (previewBtn) previewBtn.disabled = true;
|
|
resetProgressSteps();
|
|
if (deploymentInfo) deploymentInfo.innerHTML = '<p>Deployment information will appear here once the process begins.</p>';
|
|
}
|
|
|
|
function resetProgressStepsVisually() {
|
|
document.querySelectorAll('.deployment-step').forEach(step => {
|
|
step.classList.remove('in-progress', 'completed', 'failed'); step.classList.add('pending');
|
|
const icon = step.querySelector('.step-icon');
|
|
if(icon) { // Reset icon to number
|
|
const stepNumber = step.id.split('-')[1] === 'init' ? 1 : step.id.split('-')[1] === 'template' ? 2 : step.id.split('-')[1] === 'deploy' ? 3 : 4;
|
|
icon.innerHTML = stepNumber;
|
|
}
|
|
});
|
|
}
|
|
function resetProgressSteps() { resetProgressStepsVisually(); updateProgress(0, ''); }
|
|
function updateProgress(percent, message) { if (deploymentProgress) deploymentProgress.style.width = `${percent}%`; if (progressText) progressText.textContent = message; }
|
|
function updateStepStatus(stepId, status) {
|
|
const step = document.getElementById(stepId);
|
|
if (step) {
|
|
step.classList.remove('pending', 'in-progress', 'completed', 'failed'); step.classList.add(status);
|
|
const icon = step.querySelector('.step-icon');
|
|
if (icon) {
|
|
if (status === 'completed') icon.innerHTML = '<i class="fas fa-check"></i>';
|
|
else if (status === 'failed') icon.innerHTML = '<i class="fas fa-times"></i>';
|
|
else if (status === 'in-progress') icon.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
|
else { const stepNumber = stepId.split('-')[1] === 'init' ? 1 : stepId.split('-')[1] === 'template' ? 2 : stepId.split('-')[1] === 'deploy' ? 3 : 4; icon.innerHTML = stepNumber; }
|
|
}
|
|
} else { console.warn(`Step element with ID ${stepId} not found.`); }
|
|
}
|
|
function updateDeploymentInfo(data) {
|
|
if (!deploymentInfo) return;
|
|
let html = '<dl>';
|
|
if (data.deployment_id) html += `<dt>Deployment ID:</dt><dd>${data.deployment_id}</dd>`;
|
|
if (data.template || selectedTemplate) html += `<dt>Template:</dt><dd>${data.template || selectedTemplate}</dd>`;
|
|
let statusText = data.status ? data.status.charAt(0).toUpperCase() + data.status.slice(1) : 'Unknown';
|
|
html += `<dt>Status:</dt><dd><span class="status-badge status-${data.status || 'unknown'}">${statusText}</span></dd>`;
|
|
if (data.timestamp) { try { html += `<dt>Timestamp:</dt><dd>${new Date(data.timestamp).toLocaleString()}</dd>`; } catch (e) { console.error("Invalid timestamp", data.timestamp); } }
|
|
if (data.status === 'completed') { const url = data.deployment_url || `https://${data.deployment_id}.${BASE_DOMAIN}`; html += `<dt>URL:</dt><dd><a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a></dd>`; } // Added rel
|
|
if (data.status === 'failed' && data.error) html += `<dt style="color: var(--error);">Error:</dt><dd style="color: var(--error);">${data.error}</dd>`;
|
|
html += '</dl>';
|
|
deploymentInfo.innerHTML = html;
|
|
}
|
|
|
|
// Initialize
|
|
initUI();
|
|
});
|
|
</script> |