Newer
Older
EnvoyControlPlane / static / tools / storage_client.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DB Storage Dump/Restore Client</title>
    <style>
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); }
        h1 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; margin-bottom: 20px; }
        h2 { color: #007bff; margin-top: 30px; border-left: 5px solid #007bff; padding-left: 10px; }
        .section { margin-bottom: 30px; padding: 15px; background-color: #f9f9f9; border-radius: 8px; }
        label { display: block; margin-bottom: 8px; font-weight: 600; color: #555; }
        input[type="file"], select { padding: 10px; border: 1px solid #ccc; border-radius: 4px; width: 100%; box-sizing: border-box; margin-top: 5px; }
        button {
            padding: 10px 15px;
            background-color: #28a745;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-top: 10px;
            font-size: 16px;
            transition: background-color 0.3s;
        }
        button:hover { background-color: #218838; }
        #status-box {
            margin-top: 20px;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #fff;
            white-space: pre-wrap;
            font-family: Consolas, monospace;
            font-size: 14px;
            min-height: 50px;
        }
        .error { border-color: red !important; background-color: #ffebeb; color: #a94442; }
        .success { border-color: green !important; background-color: #dff0d8; color: #3c763d; }
    </style>
</head>
<body>

    <h1>💾 DB Storage Operations</h1>
    <p>API Endpoint: <code>/storage-dump</code></p>

    <div class="section">
        <h2>📥 Database Dump (GET)</h2>
        <p>This will initiate a **GET** request to download the full database state as a JSON file.</p>
        <button onclick="handleDump()">Download DB Dump</button>
    </div>

    <div class="section">
        <h2>⬆️ Database Restore (POST)</h2>
        <form id="restoreForm">
            <label for="dumpFile">Select JSON Dump File:</label>
            <input type="file" id="dumpFile" name="dumpFile" accept=".json" required>

            <label for="restoreMode">Restore Mode:</label>
            <select id="restoreMode" name="restoreMode">
                <option value="merge">Merge (UPSERT)</option>
                <option value="override">Override (DELETE ALL + UPSERT)</option>
            </select>
            <button type="submit">Upload and Restore</button>
        </form>
    </div>

    <div id="status-box">Status: Ready.</div>

    <script>
        const API_ENDPOINT = "/storage-dump";
        const statusBox = document.getElementById('status-box');

        /**
         * Clears status box and updates with new message.
         * @param {string} message - The message to display.
         * @param {boolean} isError - Whether the message indicates an error.
         */
        function updateStatus(message, isError = false) {
            statusBox.className = '';
            statusBox.textContent = message;
            if (isError) {
                statusBox.classList.add('error');
            } else {
                statusBox.classList.add('success');
            }
        }

        /**
         * Handles the GET request for downloading the DB dump.
         */
        function handleDump() {
            updateStatus("Dumping database... Please wait.", false);
            
            fetch(API_ENDPOINT)
                .then(response => {
                    if (!response.ok) {
                        return response.text().then(text => {
                            throw new Error(`HTTP Error ${response.status}: ${text}`);
                        });
                    }
                    // Extract filename from Content-Disposition header, or use a default
                    const contentDisp = response.headers.get('Content-Disposition');
                    let filename = 'db_dump.json';
                    if (contentDisp) {
                        const match = contentDisp.match(/filename="(.+?)"/);
                        if (match && match[1]) {
                            filename = match[1];
                        }
                    }
                    return response.blob().then(blob => ({ blob, filename }));
                })
                .then(({ blob, filename }) => {
                    // Create a link element to trigger the download
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = url;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    window.URL.revokeObjectURL(url);
                    document.body.removeChild(a);

                    updateStatus(`Database dump downloaded successfully as **${filename}**.`, false);
                })
                .catch(error => {
                    updateStatus(`Dump failed: ${error.message}`, true);
                    console.error('Dump Error:', error);
                });
        }

        /**
         * Handles the POST request for restoring the DB from an uploaded file.
         */
        document.getElementById('restoreForm').addEventListener('submit', function(e) {
            e.preventDefault();
            
            const fileInput = document.getElementById('dumpFile');
            const mode = document.getElementById('restoreMode').value;
            const file = fileInput.files[0];

            if (!file) {
                updateStatus("Please select a dump file to upload.", true);
                return;
            }

            updateStatus(`Restoring database in **${mode}** mode... Uploading **${file.name}**...`, false);

            // Use a FormData object to send the file content
            const formData = new FormData();
            formData.append('data', file); // The server-side should handle reading the file content from the body

            // Read file content as text/bytes to send directly in the body
            const reader = new FileReader();
            reader.onload = function(event) {
                fetch(`${API_ENDPOINT}?mode=${mode}`, {
                    method: 'POST',
                    headers: {
                        // The file content is the body, so set Content-Type to JSON (or leave default if server reads raw body)
                        'Content-Type': 'application/json' 
                    },
                    body: event.target.result // Send the raw file content (JSON string/bytes)
                })
                .then(response => {
                    // Check if response has content before trying to parse JSON
                    const contentType = response.headers.get("content-type");
                    if (contentType && contentType.indexOf("application/json") !== -1) {
                        return response.json().then(data => ({ response, data }));
                    } else {
                        return response.text().then(text => ({ response, data: text }));
                    }
                })
                .then(({ response, data }) => {
                    if (!response.ok) {
                        const errorMessage = typeof data === 'object' && data.message ? data.message : data.toString();
                        throw new Error(`HTTP Error ${response.status}: ${errorMessage}`);
                    }
                    
                    const message = typeof data === 'object' && data.message ? data.message : "Restore command acknowledged successfully.";
                    updateStatus(`Restore successful! ${message}`, false);
                })
                .catch(error => {
                    updateStatus(`Restore failed: ${error.message}`, true);
                    console.error('Restore Error:', error);
                });
            };
            
            // Read the file as a text string (assuming the dump file is readable JSON)
            reader.readAsText(file); 
        });
    </script>
</body>
</html>