Add initial MVP documentation for DMARQ platform, detailing backend architecture, frontend implementation, and deployment structure
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* DMARQ Integrated Frontend Styles
|
||||
*/
|
||||
|
||||
/* Setup Wizard Styles */
|
||||
.setup-progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.setup-step {
|
||||
position: relative;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.setup-step.active {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.setup-step.completed {
|
||||
background-color: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.setup-progress:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #e5e7eb;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Stats and Dashboard Styles */
|
||||
.stat {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.dashboard-stats .card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Additional Utility Classes */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Navigation Styles */
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar ul li a {
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar ul li a:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.sidebar ul li a.active {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: static;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* DMARQ Frontend Application
|
||||
* Vanilla JS implementation replacing the React frontend
|
||||
*/
|
||||
|
||||
// Global state management
|
||||
const appState = {
|
||||
isAuthenticated: false,
|
||||
isSetupComplete: null,
|
||||
currentPage: null,
|
||||
user: null,
|
||||
};
|
||||
|
||||
// API utility functions
|
||||
const api = {
|
||||
baseUrl: '/api/v1',
|
||||
|
||||
async request(endpoint, options = {}) {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {}),
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const config = {
|
||||
...options,
|
||||
headers,
|
||||
};
|
||||
|
||||
const response = await fetch(`${this.baseUrl}${endpoint}`, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'An error occurred' }));
|
||||
throw new Error(error.detail || 'API request failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// Authentication endpoints
|
||||
auth: {
|
||||
async login(username, password) {
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('username', username);
|
||||
formData.append('password', password);
|
||||
|
||||
const response = await fetch(`${api.baseUrl}/auth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Login failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
localStorage.setItem('auth_token', data.access_token);
|
||||
return data;
|
||||
},
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem('auth_token');
|
||||
appState.isAuthenticated = false;
|
||||
appState.user = null;
|
||||
router.navigate('/login');
|
||||
}
|
||||
},
|
||||
|
||||
// System endpoints
|
||||
system: {
|
||||
async health() {
|
||||
return api.request('/health');
|
||||
}
|
||||
},
|
||||
|
||||
// Domain endpoints
|
||||
domains: {
|
||||
async getAll() {
|
||||
return api.request('/domains');
|
||||
},
|
||||
|
||||
async getById(id) {
|
||||
return api.request(`/domains/${id}`);
|
||||
}
|
||||
},
|
||||
|
||||
// Reports endpoints
|
||||
reports: {
|
||||
async getAll() {
|
||||
return api.request('/reports');
|
||||
},
|
||||
|
||||
async getById(id) {
|
||||
return api.request(`/reports/${id}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Simple router implementation
|
||||
const router = {
|
||||
routes: {
|
||||
'/': () => handleHome(),
|
||||
'/login': () => renderLogin(),
|
||||
'/dashboard': () => renderDashboard(),
|
||||
'/setup': () => renderSetup()
|
||||
},
|
||||
|
||||
init() {
|
||||
// Initial route handling
|
||||
window.addEventListener('popstate', () => this.handleRouteChange());
|
||||
|
||||
// Handle clicks on links to use client-side routing
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.matches('a[data-route]')) {
|
||||
e.preventDefault();
|
||||
this.navigate(e.target.getAttribute('href'));
|
||||
}
|
||||
});
|
||||
|
||||
// Initial route
|
||||
this.handleRouteChange();
|
||||
},
|
||||
|
||||
handleRouteChange() {
|
||||
const path = window.location.pathname;
|
||||
const route = this.routes[path];
|
||||
|
||||
if (route) {
|
||||
route();
|
||||
appState.currentPage = path;
|
||||
} else {
|
||||
this.navigate('/');
|
||||
}
|
||||
},
|
||||
|
||||
navigate(path) {
|
||||
window.history.pushState(null, null, path);
|
||||
this.handleRouteChange();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle initial app loading
|
||||
async function initApp() {
|
||||
try {
|
||||
// Check if user is authenticated
|
||||
const token = localStorage.getItem('auth_token');
|
||||
appState.isAuthenticated = !!token;
|
||||
|
||||
// Check system setup status
|
||||
const healthData = await api.system.health().catch(() => ({ is_setup_complete: false }));
|
||||
appState.isSetupComplete = healthData.is_setup_complete;
|
||||
|
||||
// Determine which page to show
|
||||
handleHome();
|
||||
} catch (error) {
|
||||
console.error('Error initializing app:', error);
|
||||
showError('Failed to initialize the application');
|
||||
} finally {
|
||||
// Hide loading indicator
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the home route based on app state
|
||||
function handleHome() {
|
||||
if (!appState.isSetupComplete) {
|
||||
router.navigate('/setup');
|
||||
} else if (!appState.isAuthenticated) {
|
||||
router.navigate('/login');
|
||||
} else {
|
||||
router.navigate('/dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to show errors
|
||||
function showError(message) {
|
||||
const errorEl = document.createElement('div');
|
||||
errorEl.className = 'error-message';
|
||||
errorEl.textContent = message;
|
||||
errorEl.style.cssText = 'background-color: #fee2e2; color: #b91c1c; padding: 1rem; border-radius: 0.25rem; margin-bottom: 1rem;';
|
||||
|
||||
const app = document.getElementById('app');
|
||||
app.prepend(errorEl);
|
||||
|
||||
setTimeout(() => {
|
||||
errorEl.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Initialize the app when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
document.addEventListener('DOMContentLoaded', () => router.init());
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Dashboard page functionality
|
||||
*/
|
||||
|
||||
async function renderDashboard() {
|
||||
// Verify authentication
|
||||
if (!appState.isAuthenticated) {
|
||||
router.navigate('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
const appElement = document.getElementById('app');
|
||||
|
||||
// Create dashboard layout
|
||||
appElement.innerHTML = `
|
||||
<div class="navbar">
|
||||
<div class="logo">DMARQ</div>
|
||||
<div class="user-menu">
|
||||
<button id="logout-button">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="/dashboard" data-route>Dashboard</a></li>
|
||||
<li><a href="/domains" data-route>Domains</a></li>
|
||||
<li><a href="/reports" data-route>Reports</a></li>
|
||||
<li><a href="/settings" data-route>Settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<h1>Dashboard</h1>
|
||||
|
||||
<div id="loading-dashboard">Loading dashboard data...</div>
|
||||
|
||||
<div id="dashboard-content" class="hidden">
|
||||
<div class="dashboard-stats">
|
||||
<div class="card">
|
||||
<h3>Total Domains</h3>
|
||||
<div id="total-domains" class="stat">-</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Total Reports</h3>
|
||||
<div id="total-reports" class="stat">-</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Compliance Rate</h3>
|
||||
<div id="compliance-rate" class="stat">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Compliance Overview</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="compliance-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Recent Reports</h2>
|
||||
<div id="recent-reports">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="reports-table-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listener for logout
|
||||
document.getElementById('logout-button').addEventListener('click', () => {
|
||||
api.auth.logout();
|
||||
});
|
||||
|
||||
try {
|
||||
// Load dashboard data
|
||||
await loadDashboardData();
|
||||
} catch (error) {
|
||||
console.error('Error loading dashboard data:', error);
|
||||
showError('Failed to load dashboard data');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDashboardData() {
|
||||
try {
|
||||
// Fetch domains and reports data
|
||||
const [domainsResponse, reportsResponse] = await Promise.all([
|
||||
api.domains.getAll(),
|
||||
api.reports.getAll()
|
||||
]);
|
||||
|
||||
const domains = domainsResponse || [];
|
||||
const reports = reportsResponse || [];
|
||||
|
||||
// Update stats
|
||||
document.getElementById('total-domains').textContent = domains.length;
|
||||
document.getElementById('total-reports').textContent = reports.length;
|
||||
|
||||
// Calculate compliance rate
|
||||
const compliantReports = reports.filter(report => report.is_compliant);
|
||||
const complianceRate = reports.length > 0
|
||||
? Math.round((compliantReports.length / reports.length) * 100)
|
||||
: 0;
|
||||
document.getElementById('compliance-rate').textContent = `${complianceRate}%`;
|
||||
|
||||
// Render compliance chart
|
||||
renderComplianceChart(reports);
|
||||
|
||||
// Render recent reports table
|
||||
renderRecentReports(reports, domains);
|
||||
|
||||
// Hide loading, show content
|
||||
document.getElementById('loading-dashboard').classList.add('hidden');
|
||||
document.getElementById('dashboard-content').classList.remove('hidden');
|
||||
} catch (error) {
|
||||
throw new Error('Failed to load dashboard data');
|
||||
}
|
||||
}
|
||||
|
||||
function renderComplianceChart(reports) {
|
||||
if (!reports || reports.length === 0) return;
|
||||
|
||||
const canvas = document.getElementById('compliance-chart');
|
||||
if (!canvas) return;
|
||||
|
||||
// Prepare data
|
||||
const last6Months = [];
|
||||
const currentDate = new Date();
|
||||
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
const date = new Date(currentDate);
|
||||
date.setMonth(currentDate.getMonth() - i);
|
||||
const monthName = date.toLocaleString('default', { month: 'short' });
|
||||
last6Months.push({
|
||||
month: monthName,
|
||||
year: date.getFullYear(),
|
||||
reports: [],
|
||||
startDate: new Date(date.getFullYear(), date.getMonth(), 1),
|
||||
endDate: new Date(date.getFullYear(), date.getMonth() + 1, 0)
|
||||
});
|
||||
}
|
||||
|
||||
// Group reports by month
|
||||
reports.forEach(report => {
|
||||
const reportDate = new Date(report.report_date);
|
||||
const monthData = last6Months.find(monthInfo =>
|
||||
reportDate >= monthInfo.startDate && reportDate <= monthInfo.endDate
|
||||
);
|
||||
|
||||
if (monthData) {
|
||||
monthData.reports.push(report);
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate compliance rates by month
|
||||
const complianceData = last6Months.map(monthInfo => {
|
||||
if (monthInfo.reports.length === 0) return 0;
|
||||
const compliantCount = monthInfo.reports.filter(report => report.is_compliant).length;
|
||||
return Math.round((compliantCount / monthInfo.reports.length) * 100);
|
||||
});
|
||||
|
||||
const labels = last6Months.map(monthInfo => `${monthInfo.month} ${monthInfo.year}`);
|
||||
|
||||
// Create chart
|
||||
new Chart(canvas, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Compliance Rate (%)',
|
||||
data: complianceData,
|
||||
backgroundColor: '#3b82f6',
|
||||
borderColor: '#2563eb',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Compliance Rate (%)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecentReports(reports, domains) {
|
||||
if (!reports || reports.length === 0) return;
|
||||
|
||||
const tableBody = document.getElementById('reports-table-body');
|
||||
if (!tableBody) return;
|
||||
|
||||
// Sort reports by date (newest first) and take last 10
|
||||
const sortedReports = [...reports]
|
||||
.sort((a, b) => new Date(b.report_date) - new Date(a.report_date))
|
||||
.slice(0, 10);
|
||||
|
||||
// Create a lookup map for domains
|
||||
const domainMap = new Map();
|
||||
domains.forEach(domain => {
|
||||
domainMap.set(domain.id, domain.domain_name);
|
||||
});
|
||||
|
||||
// Add rows to the table
|
||||
sortedReports.forEach(report => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Format date
|
||||
const reportDate = new Date(report.report_date);
|
||||
const formattedDate = reportDate.toLocaleDateString();
|
||||
|
||||
// Get domain name
|
||||
const domainName = domainMap.get(report.domain_id) || 'Unknown';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${domainName}</td>
|
||||
<td>${formattedDate}</td>
|
||||
<td>${report.is_compliant ?
|
||||
'<span style="color: green;">Compliant</span>' :
|
||||
'<span style="color: red;">Non-compliant</span>'
|
||||
}</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Login page functionality
|
||||
*/
|
||||
|
||||
function renderLogin() {
|
||||
const appElement = document.getElementById('app');
|
||||
|
||||
// Create login form HTML
|
||||
appElement.innerHTML = `
|
||||
<div class="auth-container">
|
||||
<div class="card">
|
||||
<h1>Login to DMARQ</h1>
|
||||
<form id="login-form">
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div id="login-error" class="hidden" style="color: red; margin-top: 10px;"></div>
|
||||
<div>
|
||||
<button type="submit" id="login-button">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listener to the login form
|
||||
document.getElementById('login-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const loginButton = document.getElementById('login-button');
|
||||
const loginError = document.getElementById('login-error');
|
||||
|
||||
// Reset UI state
|
||||
loginError.classList.add('hidden');
|
||||
loginButton.disabled = true;
|
||||
loginButton.textContent = 'Logging in...';
|
||||
|
||||
try {
|
||||
await api.auth.login(username, password);
|
||||
appState.isAuthenticated = true;
|
||||
router.navigate('/dashboard');
|
||||
} catch (error) {
|
||||
loginError.textContent = 'Invalid username or password';
|
||||
loginError.classList.remove('hidden');
|
||||
} finally {
|
||||
loginButton.disabled = false;
|
||||
loginButton.textContent = 'Login';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Setup Wizard functionality
|
||||
*/
|
||||
|
||||
function renderSetup() {
|
||||
const appElement = document.getElementById('app');
|
||||
|
||||
// Create setup wizard layout
|
||||
appElement.innerHTML = `
|
||||
<div class="auth-container" style="max-width: 600px;">
|
||||
<div class="card">
|
||||
<h1>DMARQ Setup Wizard</h1>
|
||||
|
||||
<div class="setup-progress">
|
||||
<div class="setup-step active" id="step-1">1. Admin Account</div>
|
||||
<div class="setup-step" id="step-2">2. System Configuration</div>
|
||||
<div class="setup-step" id="step-3">3. Email Configuration</div>
|
||||
</div>
|
||||
|
||||
<div id="setup-content">
|
||||
<!-- Step 1: Admin Account -->
|
||||
<div id="setup-step-1" class="setup-form">
|
||||
<h2>Create Admin Account</h2>
|
||||
<form id="admin-setup-form">
|
||||
<div>
|
||||
<label for="admin-email">Email</label>
|
||||
<input type="email" id="admin-email" name="admin-email" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="admin-username">Username</label>
|
||||
<input type="text" id="admin-username" name="admin-username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="admin-password">Password</label>
|
||||
<input type="password" id="admin-password" name="admin-password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="admin-password-confirm">Confirm Password</label>
|
||||
<input type="password" id="admin-password-confirm" name="admin-password-confirm" required>
|
||||
</div>
|
||||
<div id="admin-error" class="hidden" style="color: red; margin-top: 10px;"></div>
|
||||
<div style="margin-top: 20px;">
|
||||
<button type="submit" id="admin-next-button">Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: System Configuration -->
|
||||
<div id="setup-step-2" class="setup-form hidden">
|
||||
<h2>System Configuration</h2>
|
||||
<form id="system-setup-form">
|
||||
<div>
|
||||
<label for="app-name">Application Name</label>
|
||||
<input type="text" id="app-name" name="app-name" value="DMARQ" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="base-url">Base URL</label>
|
||||
<input type="url" id="base-url" name="base-url" placeholder="https://your-dmarq-instance.com" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" id="enable-cloudflare" name="enable-cloudflare">
|
||||
Enable Cloudflare Integration
|
||||
</label>
|
||||
</div>
|
||||
<div id="cloudflare-settings" class="hidden">
|
||||
<div>
|
||||
<label for="cloudflare-token">Cloudflare API Token</label>
|
||||
<input type="password" id="cloudflare-token" name="cloudflare-token">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cloudflare-zone">Cloudflare Zone ID</label>
|
||||
<input type="text" id="cloudflare-zone" name="cloudflare-zone">
|
||||
</div>
|
||||
</div>
|
||||
<div id="system-error" class="hidden" style="color: red; margin-top: 10px;"></div>
|
||||
<div style="margin-top: 20px; display: flex; justify-content: space-between;">
|
||||
<button type="button" id="system-prev-button">Previous</button>
|
||||
<button type="submit" id="system-next-button">Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Email Configuration -->
|
||||
<div id="setup-step-3" class="setup-form hidden">
|
||||
<h2>Email Configuration</h2>
|
||||
<form id="email-setup-form">
|
||||
<div>
|
||||
<label for="imap-server">IMAP Server</label>
|
||||
<input type="text" id="imap-server" name="imap-server" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-port">IMAP Port</label>
|
||||
<input type="number" id="imap-port" name="imap-port" value="993" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-username">IMAP Username</label>
|
||||
<input type="text" id="imap-username" name="imap-username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-password">IMAP Password</label>
|
||||
<input type="password" id="imap-password" name="imap-password" required>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="test-imap-button">Test Connection</button>
|
||||
<span id="test-imap-result"></span>
|
||||
</div>
|
||||
<div id="email-error" class="hidden" style="color: red; margin-top: 10px;"></div>
|
||||
<div style="margin-top: 20px; display: flex; justify-content: space-between;">
|
||||
<button type="button" id="email-prev-button">Previous</button>
|
||||
<button type="submit" id="email-finish-button">Finish Setup</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listeners and setup functionality for the wizard
|
||||
setupWizardEventListeners();
|
||||
}
|
||||
|
||||
function setupWizardEventListeners() {
|
||||
// Step 1: Admin Account setup
|
||||
const adminForm = document.getElementById('admin-setup-form');
|
||||
if (adminForm) {
|
||||
adminForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const email = document.getElementById('admin-email').value;
|
||||
const username = document.getElementById('admin-username').value;
|
||||
const password = document.getElementById('admin-password').value;
|
||||
const confirmPassword = document.getElementById('admin-password-confirm').value;
|
||||
const errorElement = document.getElementById('admin-error');
|
||||
|
||||
// Simple validation
|
||||
if (password !== confirmPassword) {
|
||||
errorElement.textContent = 'Passwords do not match';
|
||||
errorElement.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store values (in a real app, you'd save these to the server)
|
||||
localStorage.setItem('setup_admin_email', email);
|
||||
localStorage.setItem('setup_admin_username', username);
|
||||
|
||||
// Move to step 2
|
||||
goToStep(2);
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2: System Configuration
|
||||
const systemForm = document.getElementById('system-setup-form');
|
||||
if (systemForm) {
|
||||
// Toggle Cloudflare settings visibility
|
||||
const enableCloudflare = document.getElementById('enable-cloudflare');
|
||||
const cloudflareSettings = document.getElementById('cloudflare-settings');
|
||||
|
||||
enableCloudflare.addEventListener('change', () => {
|
||||
if (enableCloudflare.checked) {
|
||||
cloudflareSettings.classList.remove('hidden');
|
||||
} else {
|
||||
cloudflareSettings.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Previous button
|
||||
document.getElementById('system-prev-button').addEventListener('click', () => {
|
||||
goToStep(1);
|
||||
});
|
||||
|
||||
// Next button
|
||||
systemForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Store values
|
||||
const appName = document.getElementById('app-name').value;
|
||||
const baseUrl = document.getElementById('base-url').value;
|
||||
|
||||
localStorage.setItem('setup_app_name', appName);
|
||||
localStorage.setItem('setup_base_url', baseUrl);
|
||||
|
||||
if (enableCloudflare.checked) {
|
||||
const cloudflareToken = document.getElementById('cloudflare-token').value;
|
||||
const cloudflareZone = document.getElementById('cloudflare-zone').value;
|
||||
|
||||
localStorage.setItem('setup_cloudflare_enabled', 'true');
|
||||
localStorage.setItem('setup_cloudflare_token', cloudflareToken);
|
||||
localStorage.setItem('setup_cloudflare_zone', cloudflareZone);
|
||||
}
|
||||
|
||||
// Move to step 3
|
||||
goToStep(3);
|
||||
});
|
||||
}
|
||||
|
||||
// Step 3: Email Configuration
|
||||
const emailForm = document.getElementById('email-setup-form');
|
||||
if (emailForm) {
|
||||
// Previous button
|
||||
document.getElementById('email-prev-button').addEventListener('click', () => {
|
||||
goToStep(2);
|
||||
});
|
||||
|
||||
// Test IMAP connection
|
||||
document.getElementById('test-imap-button').addEventListener('click', async () => {
|
||||
const testButton = document.getElementById('test-imap-button');
|
||||
const resultSpan = document.getElementById('test-imap-result');
|
||||
|
||||
testButton.disabled = true;
|
||||
testButton.textContent = 'Testing...';
|
||||
resultSpan.textContent = '';
|
||||
|
||||
try {
|
||||
// In a real app, you'd make an API call to test the connection
|
||||
// Here we'll just simulate it
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
resultSpan.textContent = '✓ Connection successful';
|
||||
resultSpan.style.color = 'green';
|
||||
} catch (error) {
|
||||
resultSpan.textContent = '✗ Connection failed';
|
||||
resultSpan.style.color = 'red';
|
||||
} finally {
|
||||
testButton.disabled = false;
|
||||
testButton.textContent = 'Test Connection';
|
||||
}
|
||||
});
|
||||
|
||||
// Finish setup
|
||||
emailForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const imapServer = document.getElementById('imap-server').value;
|
||||
const imapPort = document.getElementById('imap-port').value;
|
||||
const imapUsername = document.getElementById('imap-username').value;
|
||||
const imapPassword = document.getElementById('imap-password').value;
|
||||
|
||||
const finishButton = document.getElementById('email-finish-button');
|
||||
const errorElement = document.getElementById('email-error');
|
||||
|
||||
finishButton.disabled = true;
|
||||
finishButton.textContent = 'Completing Setup...';
|
||||
errorElement.classList.add('hidden');
|
||||
|
||||
try {
|
||||
// In a real app, you'd send all the setup data to the server
|
||||
// For this example, we'll simulate the API call
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Update app state
|
||||
appState.isSetupComplete = true;
|
||||
|
||||
// Redirect to login
|
||||
router.navigate('/login');
|
||||
} catch (error) {
|
||||
errorElement.textContent = 'Setup failed: ' + (error.message || 'Unknown error');
|
||||
errorElement.classList.remove('hidden');
|
||||
finishButton.disabled = false;
|
||||
finishButton.textContent = 'Finish Setup';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function goToStep(stepNumber) {
|
||||
// Hide all steps
|
||||
document.querySelectorAll('.setup-form').forEach(form => {
|
||||
form.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Show selected step
|
||||
document.getElementById(`setup-step-${stepNumber}`).classList.remove('hidden');
|
||||
|
||||
// Update step indicators
|
||||
document.querySelectorAll('.setup-step').forEach((step, index) => {
|
||||
if (index + 1 === stepNumber) {
|
||||
step.classList.add('active');
|
||||
} else if (index + 1 < stepNumber) {
|
||||
step.classList.add('completed');
|
||||
step.classList.remove('active');
|
||||
} else {
|
||||
step.classList.remove('active', 'completed');
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user