269 lines
15 KiB
HTML
269 lines
15 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>
|
|
</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 %} |