Auto-sync from website-creator

This commit is contained in:
Kunthawat Greethong
2026-03-08 23:03:19 +07:00
commit 9be686f587
117 changed files with 24737 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
# Website Configuration
# Fill these after generating your website
# Umami Analytics (Optional - Self-hosted)
# Get from: Your Umami dashboard → Settings → Websites
UMAMI_WEBSITE_ID=
UMAMI_DOMAIN=analytics.example.com
# Admin Dashboard
# Change this before deploying to production!
ADMIN_PASSWORD=changeme
# Database (Optional - for production with Turso)
# ASTRO_DB_REMOTE_URL=libsql://your-db.turso.io
# ASTRO_DB_APP_TOKEN=your-turso-token
# Site Configuration
SITE_URL=https://your-domain.com
SITE_NAME="Your Website Name"

View File

@@ -0,0 +1,408 @@
#!/usr/bin/env python3
"""
Website Creator - Generate PDPA-compliant Astro websites
Creates complete Astro projects with:
- Bilingual support (Thai/English)
- Umami Analytics integration (auto-create)
- GA4 Analytics support (existing or new)
- Google Search Console setup
- Cookie consent management
- Consent logging database (Astro DB)
- PDPA-compliant legal pages
- Easypanel deployment
Usage:
python3 create_astro_website.py \
--name "Deal Plus Tech" \
--type "corporate" \
--languages "th,en" \
--output "./dealplustech-website"
"""
import os
import sys
import argparse
import shutil
import subprocess
from pathlib import Path
from datetime import datetime
# ============================================================================
# INTERACTIVE SETUP FUNCTIONS
# ============================================================================
def ask_analytics_setup():
"""
Interactive analytics setup workflow
Returns:
dict: Analytics configuration
"""
print("\n" + "=" * 60)
print("📊 ANALYTICS SETUP")
print("=" * 60)
config = {
'search_console': None,
'analytics_type': None, # 'umami' or 'ga4'
'umami_auto_create': False,
'umami_website_id': None,
'ga4_property_id': None,
'ga4_credentials_path': None,
'ga4_existing': False
}
# Step 1: Google Search Console (for all websites)
print("\n1⃣ Google Search Console Setup")
print(" GSC is recommended for all websites for SEO monitoring.")
gsc_choice = input("\n Do you want to setup Google Search Console? (y/n): ").strip().lower()
if gsc_choice == 'y':
print("\n GSC Setup Options:")
print(" 1. I'll add it manually later (skip for now)")
print(" 2. I have service account credentials file")
gsc_method = input("\n Choose option (1-2): ").strip()
if gsc_method == '2':
gsc_path = input(" Enter path to GSC credentials file: ").strip()
if os.path.exists(gsc_path):
config['search_console'] = {
'credentials_path': gsc_path,
'setup_later': False
}
print(" ✓ GSC credentials loaded")
else:
print(" ⚠ File not found, will setup later")
config['search_console'] = {'setup_later': True}
else:
config['search_console'] = {'setup_later': True}
print(" ✓ Will setup later")
else:
print(" ⏭️ Skipping GSC setup")
# Step 2: Choose Analytics Type (Umami OR GA4)
print("\n2⃣ Analytics Platform")
print(" Choose ONE analytics platform:")
print(" 1. Umami Analytics (recommended for most users)")
print(" - Privacy-focused, self-hosted")
print(" - Simple setup, auto-created")
print(" - Good for most websites")
print("\n 2. Google Analytics 4 (for advanced users)")
print(" - Full-featured analytics")
print(" - Requires Google account")
print(" - Good for existing GA4 users")
analytics_choice = input("\n Choose analytics (1-2): ").strip()
if analytics_choice == '1':
# Umami setup
config['analytics_type'] = 'umami'
print("\n 📈 Umami Analytics Setup")
# Check if Umami credentials are configured
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
umami_url = os.getenv('UMAMI_URL', '')
umami_username = os.getenv('UMAMI_USERNAME', '')
umami_password = os.getenv('UMAMI_PASSWORD', '')
if umami_url and umami_username and umami_password:
print(" ✓ Umami credentials found in .env")
print(" ✓ Will auto-create Umami website for this project")
config['umami_auto_create'] = True
else:
print(" ⚠ Umami credentials not configured in .env")
print(" ⏭️ Skipping Umami setup (can add manually later)")
elif analytics_choice == '2':
# GA4 setup
config['analytics_type'] = 'ga4'
print("\n 🔍 Google Analytics 4 Setup")
print(" 1. Create new GA4 property (auto-setup)")
print(" 2. Use existing GA4 property (manual setup)")
ga4_choice = input("\n Choose option (1-2): ").strip()
if ga4_choice == '1':
print("\n ⚠ Auto-creating GA4 properties requires API setup.")
print(" ⏭️ Will provide instructions for manual setup")
config['ga4_existing'] = False
else:
print("\n Please provide your existing GA4 details:")
# Check unified .env for GA4 credentials
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
ga4_property_id = os.getenv('GA4_PROPERTY_ID', '')
ga4_credentials_path = os.getenv('GA4_CREDENTIALS_PATH', '')
if ga4_property_id:
print(f" Found GA4 Property ID in .env: {ga4_property_id[:20]}...")
use_global = input(" Use this for this project? (y/n): ").strip().lower()
if use_global == 'y':
config['ga4_property_id'] = ga4_property_id
config['ga4_credentials_path'] = ga4_credentials_path
print(" ✓ Using global GA4 credentials")
else:
config['ga4_property_id'] = input(" Enter GA4 Property ID: ").strip()
config['ga4_credentials_path'] = input(" Enter GA4 credentials file path: ").strip()
else:
config['ga4_property_id'] = input(" Enter GA4 Property ID (G-XXXXXXXXXX): ").strip()
config['ga4_credentials_path'] = input(" Enter GA4 credentials file path: ").strip()
config['ga4_existing'] = True
else:
print(" ⏭️ Skipping analytics setup")
return config
# ============================================================================
# TEMPLATES (abbreviated for brevity)
# ============================================================================
ASTRO_CONFIG_TEMPLATE = """import {{ defineConfig }} from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import db from '@astrojs/db';
import sitemap from '@astrojs/sitemap';
export default defineConfig({{
site: '{site_url}',
output: 'hybrid',
i18n: {{
locales: [{locales}],
defaultLocale: '{default_locale}',
routing: {{
prefixDefaultLocale: false,
fallbackType: 'rewrite',
}},
fallback: {{
th: 'en',
}},
}},
integrations: [
tailwindcss(),
db(),
sitemap({{
i18n: {{
defaultLocale: '{default_locale}',
}},
}}),
],
}});
"""
PACKAGE_JSON_TEMPLATE = """{{
"name": "{name}",
"type": "module",
"version": "1.0.0",
"scripts": {{
"dev": "astro dev",
"build": "astro build --remote",
"preview": "astro preview",
"astro": "astro",
"db:push": "astro db push --remote",
"db:seed": "astro db seed"
}},
"dependencies": {{
"astro": "^5.17.1",
"@astrojs/db": "^0.14.0",
"@astrojs/sitemap": "^3.2.0",
"@tailwindcss/vite": "^4.2.1",
"tailwindcss": "^4.2.1",
"astro-consent": "^1.0.0",
"drizzle-orm": "^0.38.0",
"@libsql/client": "^0.14.0"
}}
}}
"""
# ... (rest of templates remain the same)
# ============================================================================
# MAIN FUNCTION
# ============================================================================
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description='Create PDPA-compliant Astro website')
parser.add_argument('--name', required=True, help='Website name')
parser.add_argument('--type', default='corporate',
choices=['corporate', 'portfolio', 'landing', 'blog', 'ecommerce'],
help='Website type')
parser.add_argument('--languages', default='th,en',
help='Languages (comma-separated): th, en')
parser.add_argument('--primary-color', default='#2563eb',
help='Primary color (hex)')
parser.add_argument('--secondary-color', default='#1e40af',
help='Secondary color (hex)')
parser.add_argument('--features', default='blog,contact',
help='Features (comma-separated): blog, products, contact, portfolio')
parser.add_argument('--umami-id', default='',
help='Umami Website ID')
parser.add_argument('--umami-domain', default='analytics.example.com',
help='Umami domain')
parser.add_argument('--admin-password', default='changeme',
help='Admin password for consent logs')
parser.add_argument('--output', '-o', default='.',
help='Output directory')
parser.add_argument('--no-interactive', action='store_true',
help='Skip interactive setup (use defaults)')
args = parser.parse_args()
# Load unified credentials
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
# Get Umami credentials for auto-setup
args.umami_url = os.getenv('UMAMI_URL', '')
args.umami_username = os.getenv('UMAMI_USERNAME', '')
args.umami_password = os.getenv('UMAMI_PASSWORD', '')
args.auto_setup_umami = bool(args.umami_url and args.umami_username and args.umami_password)
languages = [lang.strip() for lang in args.languages.split(',')]
default_locale = 'en' if 'en' in languages else languages[0]
features = [f.strip() for f in args.features.split(',')]
print(f"Creating website: {args.name}")
print(f"Type: {args.type}")
print(f"Languages: {languages}")
print(f"Features: {features}")
print(f"Output: {args.output}")
# Interactive analytics setup (if not in no-interactive mode)
analytics_config = None
if not args.no_interactive:
analytics_config = ask_analytics_setup()
# Create project structure
create_project(args, languages, default_locale, features)
# Save analytics configuration to project
if analytics_config:
save_analytics_config(args.output, analytics_config)
# Auto-setup Umami if credentials provided
umami_website_id = args.umami_id
if args.auto_setup_umami and (not analytics_config or analytics_config.get('analytics_type') == 'umami'):
print("\n📈 Setting up Umami Analytics...")
try:
from umami_integration import setup_umami_for_website
website_domain = args.name.lower().replace(' ', '-') + '.moreminimore.com'
success, result = setup_umami_for_website(
args.umami_url,
args.umami_username,
args.umami_password,
args.name,
website_domain,
args.output
)
if success:
umami_website_id = result['website_id']
print(f" ✓ Umami website created: {umami_website_id}")
else:
print(f" ⚠ Umami setup skipped: {result.get('error', 'Unknown error')}")
except Exception as e:
print(f" ⚠ Umami setup failed: {e}")
print(" Continuing without Umami...")
print(f"\n✅ Website created successfully at: {args.output}")
# Update .env with Umami ID if auto-setup
env_file = os.path.join(args.output, '.env')
if os.path.exists(env_file) and umami_website_id:
with open(env_file, 'a', encoding='utf-8') as f:
f.write(f'\n# Umami Analytics (auto-configured)\n')
f.write(f'UMAMI_WEBSITE_ID={umami_website_id}\n')
print(f" ✓ Umami ID added to .env")
print("\nNext steps:")
print(f" 1. cd {args.output}")
print(" 2. npm install")
print(" 3. Update .env with your credentials")
print(" 4. npm run dev")
# Auto-deploy (always on)
print("")
print("=" * 60)
print("🚀 AUTO-DEPLOY STARTING")
print("=" * 60)
print("")
# Step 1: Sync to Gitea
print("📦 Step 1/3: Syncing to Gitea...")
git_url = sync_to_gitea(args.output, args.name)
# Step 2: Deploy to Easypanel
print("")
print("🚀 Step 2/3: Deploying to Easypanel...")
deployment_url = deploy_to_easypanel(args.output, args.name, git_url)
# Step 3: Verify and monitor
print("")
print("📊 Step 3/3: Monitoring deployment...")
monitor_deployment(args.name)
# Final output
print("")
print("=" * 60)
print("✅ COMPLETE!")
print("=" * 60)
print("")
print(f"📁 Website generated: {args.output}")
print(f"🌐 Gitea Repository: {git_url.replace('.git', '')}")
print(f"🚀 Easypanel Deployment: {deployment_url}")
print("")
print("📋 Next steps:")
print(f" 1. Website is deploying to: {deployment_url}")
print(f" 2. Check status at: https://panelwebsite.moreminimore.com")
print(f" 3. Edit Umami config: cd {args.output} && nano .env")
print("")
def save_analytics_config(output_path: str, config: dict):
"""Save analytics configuration to project context"""
context_dir = os.path.join(output_path, 'context')
os.makedirs(context_dir, exist_ok=True)
# Save data-services.json
data_services = {
'ga4': {
'enabled': config.get('analytics_type') == 'ga4',
'property_id': config.get('ga4_property_id', ''),
'credentials_path': config.get('ga4_credentials_path', '')
} if config.get('analytics_type') == 'ga4' else {'enabled': False},
'gsc': {
'enabled': config.get('search_console') is not None,
'site_url': '',
'credentials_path': config.get('search_console', {}).get('credentials_path', '')
},
'umami': {
'enabled': config.get('analytics_type') == 'umami',
'api_url': os.getenv('UMAMI_URL', ''),
'website_id': config.get('umami_website_id', '')
} if config.get('analytics_type') == 'umami' else {'enabled': False},
'dataforseo': {'enabled': False}
}
with open(os.path.join(context_dir, 'data-services.json'), 'w', encoding='utf-8') as f:
json.dump(data_services, f, indent=2)
print(f" ✓ Analytics config saved to context/data-services.json")
# ... (rest of functions remain the same - create_project, sync_to_gitea, etc.)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
requests>=2.28.0

View File

@@ -0,0 +1,313 @@
---
// Password-protected admin page for viewing consent logs
import { db, ConsentLog, desc } from 'astro:db';
// Simple password protection (in production, use proper auth)
const ADMIN_PASSWORD = Astro.env.ADMIN_PASSWORD || 'changeme';
let logs = [];
let isAuthenticated = false;
let error = '';
if (Astro.request.method === 'POST') {
const formData = await Astro.request.formData();
const password = formData.get('password');
if (password === ADMIN_PASSWORD) {
isAuthenticated = true;
try {
logs = await db.select().from(ConsentLog).orderBy(desc(ConsentLog.timestamp)).limit(100);
} catch (err) {
error = 'Failed to load consent logs. Make sure database is initialized.';
console.error(err);
}
} else {
error = 'Invalid password';
}
}
---
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Consent Logs Admin | PDPA Compliance</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f3f4f6;
padding: 2rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
font-size: 2rem;
font-weight: bold;
margin-bottom: 1.5rem;
color: #111827;
}
.login-form {
max-width: 400px;
background: white;
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 0.5rem;
color: #374151;
}
input[type="password"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
font-size: 1rem;
}
input[type="password"]:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
button {
width: 100%;
padding: 0.75rem 1.5rem;
background: #2563eb;
color: white;
border: none;
border-radius: 0.375rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
button:hover {
background: #1d4ed8;
}
.error {
background: #fee2e2;
color: #dc2626;
padding: 0.75rem;
border-radius: 0.375rem;
margin-bottom: 1rem;
font-size: 0.875rem;
}
.success {
background: #dcfce7;
color: #16a34a;
padding: 0.75rem;
border-radius: 0.375rem;
margin-bottom: 1rem;
font-size: 0.875rem;
}
table {
width: 100%;
background: white;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background: #f9fafb;
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6b7280;
}
tr:hover {
background: #f9fafb;
}
.actions {
margin-bottom: 1rem;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
font-size: 0.875rem;
border-radius: 0.375rem;
text-decoration: none;
transition: background 0.2s;
}
.btn-primary {
background: #2563eb;
color: white;
}
.btn-primary:hover {
background: #1d4ed8;
}
.btn-danger {
background: #dc2626;
color: white;
border: none;
cursor: pointer;
}
.btn-danger:hover {
background: #b91c1c;
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
border-radius: 9999px;
font-weight: 500;
}
.badge-green {
background: #dcfce7;
color: #16a34a;
}
.badge-red {
background: #fee2e2;
color: #dc2626;
}
</style>
</head>
<body>
<div class="container">
<h1>🔐 Consent Logs Admin Dashboard</h1>
{!isAuthenticated ? (
<div class="login-form">
<h2 class="text-xl font-bold mb-4">Admin Login</h2>
{error && <div class="error">{error}</div>}
<form method="POST">
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
placeholder="Enter admin password"
/>
</div>
<button type="submit">Login</button>
</form>
<p class="mt-4 text-sm text-gray-600">
Default password: <code>changeme</code> (change in .env)
</p>
</div>
) : (
<div>
<div class="actions flex gap-4 mb-4">
<a href="/admin/consent-logs" class="btn btn-primary">Refresh</a>
<a href="/" class="btn" style="background: #6b7280; color: white;">← Back to Site</a>
</div>
{error && <div class="error">{error}</div>}
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>Date/Time</th>
<th>Locale</th>
<th>Session ID</th>
<th>Essential</th>
<th>Analytics</th>
<th>Marketing</th>
<th>Policy Ver</th>
<th>IP Hash</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{logs.length === 0 ? (
<tr>
<td colspan="9" style="text-align: center; padding: 2rem;">
No consent logs found. Make sure the website has received consent.
</td>
</tr>
) : (
logs.map((log) => (
<tr>
<td>{new Date(log.timestamp).toLocaleString('en-GB')}</td>
<td>{log.locale.toUpperCase()}</td>
<td style="font-family: monospace; font-size: 0.75rem;">{log.sessionId}</td>
<td>
<span class="badge badge-green">{log.essential ? 'Yes' : 'No'}</span>
</td>
<td>
{log.analytics ? (
<span class="badge badge-green">✓</span>
) : (
<span class="badge badge-red">✗</span>
)}
</td>
<td>
{log.marketing ? (
<span class="badge badge-green">✓</span>
) : (
<span class="badge badge-red">✗</span>
)}
</td>
<td>{log.policyVersion}</td>
<td style="font-family: monospace; font-size: 0.75rem;">{log.ipHash}</td>
<td>
<button
class="btn btn-danger"
onclick="deleteConsent('{log.sessionId}')"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem;"
>
Delete
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
<div style="margin-top: 1rem; padding: 1rem; background: #fef3c7; border-radius: 0.375rem;">
<h3 style="font-size: 0.875rem; font-weight: 600; margin-bottom: 0.5rem;">⚠️ Important Notes:</h3>
<ul style="font-size: 0.75rem; color: #92400e; list-style: disc; padding-left: 1.5rem;">
<li>Consent records must be retained for 10 years (PDPA requirement)</li>
<li>Only delete records when user exercises "right to be forgotten"</li>
<li>Document all deletions for compliance audit</li>
<li>IP addresses are hashed for privacy protection</li>
</ul>
</div>
</div>
)}
</div>
<script>
async function deleteConsent(sessionId) {
if (!confirm('Delete this consent record? This action cannot be undone.')) {
return;
}
try {
const response = await fetch(`/api/consent/${sessionId}`, {
method: 'DELETE',
});
if (response.ok) {
alert('Consent record deleted successfully');
location.reload();
} else {
alert('Failed to delete consent record');
}
} catch (error) {
console.error('Delete error:', error);
alert('Error deleting consent record');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,213 @@
#!/usr/bin/env python3
"""
Umami Integration Helper
Integrates Umami Analytics into website creation workflow.
Auto-creates Umami website and adds tracking to Astro layout.
"""
import os
import sys
import requests
from typing import Dict, Optional, Tuple
from datetime import datetime
class UmamiIntegration:
"""Handle Umami website creation and tracking integration"""
def __init__(self, umami_url: str, username: str, password: str):
"""
Initialize Umami integration
Args:
umami_url: Umami instance URL
username: Umami username
password: Umami password
"""
self.umami_url = umami_url.rstrip('/')
self.api_url = f"{self.umami_url}/api"
self.username = username
self.password = password
self.token = None
self.user_id = None
def login(self) -> Tuple[bool, str]:
"""Login to Umami"""
try:
url = f"{self.api_url}/auth/login"
data = {'username': self.username, 'password': self.password}
response = requests.post(url, json=data, timeout=10)
response.raise_for_status()
result = response.json()
if 'token' in result:
self.token = result['token']
self.user_id = result.get('user', {}).get('id')
return True, "Login successful"
else:
return False, "No token in response"
except requests.exceptions.RequestException as e:
return False, f"Login failed: {str(e)}"
def create_website(self, website_name: str, website_domain: str) -> Tuple[bool, Dict]:
"""
Create Umami website
Args:
website_name: Name for Umami website
website_domain: Website domain
Returns:
(success, result_dict)
"""
# Login first
success, message = self.login()
if not success:
return False, {'error': message}
try:
# Create website
url = f"{self.api_url}/websites"
data = {'name': website_name, 'domain': website_domain}
headers = {
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/json'
}
response = requests.post(url, json=data, headers=headers, timeout=10)
response.raise_for_status()
result = response.json()
return True, {
'website_id': result.get('id'),
'name': result.get('name'),
'domain': result.get('domain'),
'tracking_script': self._get_tracking_script(result.get('id'))
}
except requests.exceptions.RequestException as e:
return False, {'error': f"Create website failed: {str(e)}"}
def _get_tracking_script(self, website_id: str) -> str:
"""Generate tracking script HTML"""
return f'<script defer src="{self.umami_url}/script.js" data-website-id="{website_id}"></script>'
def add_tracking_to_layout(self, layout_file: str, website_id: str) -> Tuple[bool, str]:
"""
Add Umami tracking to Astro layout
Args:
layout_file: Path to Astro layout file
website_id: Umami website ID
Returns:
(success, message)
"""
try:
if not os.path.exists(layout_file):
return False, f"Layout file not found: {layout_file}"
# Read layout
with open(layout_file, 'r', encoding='utf-8') as f:
content = f.read()
# Add tracking before </head>
tracking_script = self._get_tracking_script(website_id)
if '</head>' in content:
# Insert before </head>
indent = ' '
content = content.replace(
'</head>',
f'{indent}{tracking_script}\n </head>'
)
else:
# Add at end
content += f'\n{tracking_script}\n'
# Write back
with open(layout_file, 'w', encoding='utf-8') as f:
f.write(content)
return True, f"Tracking added to {layout_file}"
except Exception as e:
return False, f"Failed to add tracking: {str(e)}"
def setup_umami_for_website(
umami_url: str,
username: str,
password: str,
website_name: str,
website_domain: str,
website_repo: str
) -> Tuple[bool, Dict]:
"""
Complete Umami setup for new website
Args:
umami_url: Umami instance URL
username: Umami username
password: Umami password
website_name: Name for website
website_domain: Website domain
website_repo: Path to website repository
Returns:
(success, result_dict)
"""
print(f"\n📈 Setting up Umami Analytics...")
print(f" URL: {umami_url}")
print(f" Website: {website_name}")
# Initialize integration
umami = UmamiIntegration(umami_url, username, password)
# Step 1: Create Umami website
print(f" Creating Umami website...")
success, result = umami.create_website(website_name, website_domain)
if not success:
print(f" ✗ Failed: {result.get('error', 'Unknown error')}")
return False, result
website_id = result.get('website_id')
print(f" ✓ Created: {website_id}")
# Step 2: Add tracking to Astro layout
print(f" Adding tracking to website...")
# Find layout file
layout_paths = [
os.path.join(website_repo, 'src/layouts/BaseHead.astro'),
os.path.join(website_repo, 'src/layouts/Layout.astro'),
os.path.join(website_repo, 'src/pages/_document.tsx')
]
layout_file = None
for path in layout_paths:
if os.path.exists(path):
layout_file = path
break
if layout_file:
success, message = umami.add_tracking_to_layout(layout_file, website_id)
if success:
print(f"{message}")
else:
print(f"{message}")
else:
print(f" ⚠ No layout file found - manual tracking setup required")
return True, {
'website_id': website_id,
'name': website_name,
'domain': website_domain,
'tracking_script': result.get('tracking_script'),
'layout_updated': layout_file is not None
}