Files

281 lines
16 KiB
HTML

{% extends 'dashboard.html' %}
{% block page %}
<div class="p-6 max-w-4xl mx-auto">
<!-- Settings Navigation -->
<div class="mb-6 border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8">
<a hx-get="{{ url_for('settings.api_keys') }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
API Keys
</a>
<a hx-get="{{ url_for('settings.export') }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true"
class="border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 py-4 px-1 text-sm font-medium cursor-pointer">
Export Data
</a>
<a hx-get="{{ url_for('settings.database_schema') }}" hx-target="#container" hx-swap="innerHTML"
hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Database Schema
</a>
<a hx-get="{{ url_for('settings.login_history') }}" hx-target="#container" hx-swap="innerHTML"
hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Login History
</a>
<a hx-get="{{ url_for('settings.account') }}" hx-target="#container" hx-swap="innerHTML"
hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Account
</a>
</nav>
</div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Export Your Data</h1>
<p class="text-gray-600 dark:text-gray-400">Download all your data in JSON format for backup or
migration
purposes.</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">What's Included</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="even odd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">HTTP Functions</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">All your HTTP functions with
code and settings
</p>
</div>
</div>
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">Timer Functions</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">All scheduled functions and
their configurations
</p>
</div>
</div>
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">Shared Environments</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Environment variables and
configurations</p>
</div>
</div>
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">API Keys</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">API key names and scopes (keys
are masked)</p>
</div>
</div>
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">Invocation Logs</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Last 100 invocations per
function</p>
</div>
</div>
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-gray-900 dark:text-white">Version History</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Complete version history for all
functions</p>
</div>
</div>
</div>
<div
class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-6">
<div class="flex items-start space-x-3">
<svg class="w-5 h-5 text-yellow-600 dark:text-yellow-500 mt-0.5" fill="currentColor"
viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-medium text-yellow-900 dark:text-yellow-200 mb-1">Important
Information</h3>
<ul class="text-sm text-yellow-800 dark:text-yellow-300 space-y-1">
<li>• The export file contains sensitive data. Store it securely.</li>
<li>• API keys are partially masked for security (only first 8 characters
shown).</li>
<li>• The export is in JSON format for easy import/migration.</li>
<li>• File size may be large if you have many functions and invocations.</li>
</ul>
</div>
</div>
</div>
<div class="flex justify-center">
<a href="{{ url_for('settings.export', download='true') }}"
class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
<span>Export My Data</span>
</a>
</div>
</div>
<!-- Import Data Section -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Import Your Data</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">Upload a previously exported JSON file to
restore your functions and environments.</p>
<form id="import-form" enctype="multipart/form-data">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Select
JSON Export File</label>
<input type="file" id="import-file-input" name="import_file" accept=".json"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white">
</div>
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4">
<h3 class="font-medium text-blue-900 dark:text-blue-200 mb-2">Import Behavior</h3>
<ul class="text-sm text-blue-800 dark:text-blue-300 space-y-1">
<li>• Functions with duplicate names will be <strong>skipped</strong></li>
<li>• Successfully imported items will be listed in green</li>
<li>• Skipped items will be shown in yellow with reasons</li>
</ul>
</div>
<button type="submit" id="import-btn"
class="px-6 py-3 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition-colors flex items-center space-x-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
<span>Import Data</span>
</button>
</form>
<div id="import-results" class="hidden mt-6">
<h3 class="font-medium text-gray-900 dark:text-white mb-3">Import Results</h3>
<div id="import-summary" class="grid grid-cols-3 gap-4 mb-4"></div>
<div id="import-details"></div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h3 class="font-medium text-gray-900 dark:text-white mb-2">What to Do With Your Export</h3>
<ul class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li><strong>Backup:</strong> Save the file in a secure location for disaster recovery</li>
<li><strong>Migration:</strong> Use the export to migrate to another instance or platform
</li>
<li><strong>Analysis:</strong> Parse the JSON data for auditing or analytics purposes</li>
<li><strong>Version Control:</strong> Track changes to your functions over time</li>
</ul>
</div>
</div>
<script>
document.getElementById('import-form').addEventListener('submit', async (e) => {
e.preventDefault();
const fileInput = document.getElementById('import-file-input');
const importBtn = document.getElementById('import-btn');
const resultsDiv = document.getElementById('import-results');
if (!fileInput.files[0]) {
alert('Please select a file to import');
return;
}
importBtn.disabled = true;
const formData = new FormData();
formData.append('import_file', fileInput.files[0]);
try {
const response = await fetch('{{ url_for("settings.import_data") }}', {
method: 'POST',
body: formData
});
const data = await response.json();
if (!response.ok) {
alert('Import failed: ' + (data.error || 'Unknown error'));
return;
}
displayImportResults(data);
resultsDiv.classList.remove('hidden');
fileInput.value = '';
} catch (error) {
alert('Import error: ' + error.message);
} finally {
importBtn.disabled = false;
}
});
function displayImportResults(data) {
const summaryDiv = document.getElementById('import-summary');
const detailsDiv = document.getElementById('import-details');
summaryDiv.innerHTML = `
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3 text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">${data.summary.total_success}</div>
<div class="text-sm text-green-700 dark:text-green-300">Imported</div>
</div>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 text-center">
<div class="text-2xl font-bold text-yellow-600 dark:text-yellow-400">${data.summary.total_skipped}</div>
<div class="text-sm text-yellow-700 dark:text-yellow-300">Skipped</div>
</div>
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 text-center">
<div class="text-2xl font-bold text-red-600 dark:text-red-400">${data.summary.total_failed}</div>
<div class="text-sm text-red-700 dark:text-red-300">Failed</div>
</div>
`;
let detailsHTML = '';
for (const [type, results] of Object.entries(data.results)) {
if (results.success.length + results.skipped.length + results.failed.length > 0) {
const title = type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
detailsHTML += `<div class="mb-4"><h4 class="font-medium text-gray-900 dark:text-white mb-2">${title}</h4><div class="space-y-1">`;
results.success.forEach(msg => detailsHTML += `<div class="text-sm text-green-700 dark:text-green-300">✓ ${msg}</div>`);
results.skipped.forEach(msg => detailsHTML += `<div class="text-sm text-yellow-700 dark:text-yellow-300">⊘ ${msg}</div>`);
results.failed.forEach(msg => detailsHTML += `<div class="text-sm text-red-700 dark:text-red-300">✗ ${msg}</div>`);
detailsHTML += '</div></div>';
}
}
detailsDiv.innerHTML = detailsHTML || '<p class="text-sm text-gray-500 dark:text-gray-400">No items to import.</p>';
}
</script>
{% endblock %}