Spaces:
Sleeping
Sleeping
| <html> | |
| <head> | |
| <title>Compare APIs</title> | |
| <link | |
| rel="stylesheet" | |
| href="{{ url_for('static', path='/css/styles.css') }}" | |
| /> | |
| <style> | |
| .progress-container { | |
| display: none; | |
| width: 100%; | |
| margin: 20px 0; | |
| text-align: center; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 20px; | |
| background-color: #f3f3f3; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| .progress { | |
| width: 0%; | |
| height: 100%; | |
| background-color: #4caf50; | |
| transition: width 0.3s ease; | |
| } | |
| .loading-text { | |
| margin-top: 10px; | |
| color: #666; | |
| } | |
| .spinner { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #4caf50; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-right: 10px; | |
| } | |
| @keyframes spin { | |
| 0% { | |
| transform: rotate(0deg); | |
| } | |
| 100% { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| #processingStatus { | |
| margin-top: 10px; | |
| font-weight: bold; | |
| color: #4caf50; | |
| } | |
| .progress-steps { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 10px; | |
| position: relative; | |
| } | |
| .progress-step { | |
| background: #f3f3f3; | |
| border-radius: 50%; | |
| width: 30px; | |
| height: 30px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .progress-step.active { | |
| background: #4caf50; | |
| color: white; | |
| } | |
| .progress-step-label { | |
| position: absolute; | |
| top: 35px; | |
| font-size: 12px; | |
| width: 80px; | |
| text-align: center; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| } | |
| .step-line { | |
| position: absolute; | |
| top: 15px; | |
| height: 2px; | |
| background: #f3f3f3; | |
| width: calc(100% - 30px); | |
| left: 15px; | |
| } | |
| .step-line-progress { | |
| height: 100%; | |
| width: 0; | |
| background: #4caf50; | |
| transition: width 0.3s ease; | |
| } | |
| .session-timeout-warning { | |
| display: none; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: white; | |
| border: 1px solid #ccc; | |
| padding: 20px; | |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
| z-index: 1000; | |
| } | |
| .session-timeout-warning h3 { | |
| margin: 0 0 10px; | |
| } | |
| .session-timeout-warning p { | |
| margin: 0 0 20px; | |
| } | |
| .session-timeout-warning button { | |
| margin-right: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <img | |
| src="{{ url_for('static', path='/images/TD-logo.jpeg') }}" | |
| alt="Logo" | |
| class="logo" | |
| /> | |
| <div class="user-menu"> | |
| <div>Welcome, {{ username }}!</div> | |
| <div class="user-menu-content"> | |
| <a href="/logout">Logout</a> | |
| </div> | |
| </div> | |
| </div> | |
| {% if error %} | |
| <div class="error">{{ error }}</div> | |
| {% endif %} | |
| <div id="session-timeout" class="session-timeout-warning"> | |
| <h3>Session Timeout Warning</h3> | |
| <p> | |
| Your session will expire in | |
| <span id="timeout-countdown">60</span> seconds due to inactivity. | |
| </p> | |
| <button class="continue" onclick="extendSession()"> | |
| Continue Session | |
| </button> | |
| <button class="logout" onclick="window.location.href='/logout'"> | |
| Logout | |
| </button> | |
| </div> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-steps"> | |
| <div class="step-line"> | |
| <div class="step-line-progress" id="stepLineProgress"></div> | |
| </div> | |
| <div class="progress-step" id="step1"> | |
| 1 | |
| <div class="progress-step-label">Validating</div> | |
| </div> | |
| <div class="progress-step" id="step2"> | |
| 2 | |
| <div class="progress-step-label">Processing</div> | |
| </div> | |
| <div class="progress-step" id="step3"> | |
| 3 | |
| <div class="progress-step-label">Fetching</div> | |
| </div> | |
| <div class="progress-step" id="step4"> | |
| 4 | |
| <div class="progress-step-label">Comparing</div> | |
| </div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress" id="progressBar"></div> | |
| </div> | |
| <div class="loading-text"> | |
| <span class="spinner"></span> | |
| <span id="loadingText">Processing API requests...</span> | |
| </div> | |
| <div id="processingStatus"></div> | |
| </div> | |
| <form method="POST" action="/compare" id="compareForm"> | |
| <div class="grid"> | |
| <div> | |
| <h3>First API</h3> | |
| <div class="form-group"> | |
| <label for="api1_url">URL:</label> | |
| <input | |
| type="url" | |
| id="api1_url" | |
| name="api1_url" | |
| class="form-control" | |
| required | |
| /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="api1_method">Method:</label> | |
| <select | |
| id="api1_method" | |
| name="api1_method" | |
| class="form-control" | |
| required | |
| > | |
| {% for method in methods %} | |
| <option value="{{ method }}">{{ method }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="api1_headers">Headers (JSON):</label> | |
| <textarea | |
| id="api1_headers" | |
| name="api1_headers" | |
| class="form-control" | |
| rows="4" | |
| > | |
| {}</textarea | |
| > | |
| </div> | |
| <div class="form-group"> | |
| <label for="api1_payload">Payload (JSON):</label> | |
| <textarea | |
| id="api1_payload" | |
| name="api1_payload" | |
| class="form-control" | |
| rows="6" | |
| > | |
| {}</textarea | |
| > | |
| </div> | |
| </div> | |
| <div> | |
| <h3>Second API</h3> | |
| <div class="form-group"> | |
| <label for="api2_url">URL:</label> | |
| <input | |
| type="url" | |
| id="api2_url" | |
| name="api2_url" | |
| class="form-control" | |
| required | |
| /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="api2_method">Method:</label> | |
| <select | |
| id="api2_method" | |
| name="api2_method" | |
| class="form-control" | |
| required | |
| > | |
| {% for method in methods %} | |
| <option value="{{ method }}">{{ method }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="api2_headers">Headers (JSON):</label> | |
| <textarea | |
| id="api2_headers" | |
| name="api2_headers" | |
| class="form-control" | |
| rows="4" | |
| > | |
| {}</textarea | |
| > | |
| </div> | |
| <div class="form-group"> | |
| <label for="api2_payload">Payload (JSON):</label> | |
| <textarea | |
| id="api2_payload" | |
| name="api2_payload" | |
| class="form-control" | |
| rows="6" | |
| > | |
| {}</textarea | |
| > | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label>View Mode:</label> | |
| <div class="radio-group"> | |
| <input | |
| type="radio" | |
| id="line" | |
| name="view_mode" | |
| value="line" | |
| checked | |
| /> | |
| <label for="line">Line by Line</label> | |
| <input type="radio" id="tree" name="view_mode" value="tree" /> | |
| <label for="tree">Tree View</label> | |
| </div> | |
| </div> | |
| <button type="submit" class="button" id="compareButton"> | |
| Compare APIs | |
| </button> | |
| </form> | |
| </div> | |
| <script> | |
| // Session timeout handling | |
| let warningTimeout; | |
| let logoutTimeout; | |
| const activityEvents = [ | |
| "mousedown", | |
| "mousemove", | |
| "keypress", | |
| "scroll", | |
| "touchstart", | |
| ]; | |
| const warningTime = 19 * 60 * 1000; // Show warning 1 minute before timeout (19 minutes) | |
| const logoutTime = 20 * 60 * 1000; // Logout after 20 minutes | |
| function resetTimers() { | |
| clearTimeout(warningTimeout); | |
| clearTimeout(logoutTimeout); | |
| startTimers(); | |
| } | |
| function startTimers() { | |
| warningTimeout = setTimeout(showTimeoutWarning, warningTime); | |
| logoutTimeout = setTimeout(logout, logoutTime); | |
| } | |
| function showTimeoutWarning() { | |
| const warningDialog = document.getElementById("session-timeout"); | |
| warningDialog.style.display = "block"; | |
| // Start countdown | |
| let countdown = 60; | |
| const countdownElement = document.getElementById("timeout-countdown"); | |
| const countdownInterval = setInterval(() => { | |
| countdown--; | |
| countdownElement.textContent = countdown; | |
| if (countdown <= 0) { | |
| clearInterval(countdownInterval); | |
| } | |
| }, 1000); | |
| } | |
| function extendSession() { | |
| const warningDialog = document.getElementById("session-timeout"); | |
| warningDialog.style.display = "none"; | |
| resetTimers(); | |
| // Call backend to extend session | |
| fetch("/extend-session", { method: "POST" }); | |
| } | |
| function logout() { | |
| window.location.href = "/logout"; | |
| } | |
| // Add activity event listeners | |
| activityEvents.forEach((event) => { | |
| document.addEventListener(event, resetTimers); | |
| }); | |
| // Start timers when page loads | |
| startTimers(); | |
| document | |
| .getElementById("compareForm") | |
| .addEventListener("submit", function (e) { | |
| // Show progress container | |
| document.getElementById("progressContainer").style.display = "block"; | |
| document.getElementById("compareButton").disabled = true; | |
| // Connect to WebSocket for progress updates | |
| const ws = new WebSocket(`ws://${window.location.host}/ws`); | |
| ws.onmessage = function (event) { | |
| const data = JSON.parse(event.data); | |
| if (data.type === "processing") { | |
| updateProgress(data.status); | |
| } | |
| }; | |
| function updateProgress(status) { | |
| const progressBar = document.getElementById("progressBar"); | |
| const loadingText = document.getElementById("loadingText"); | |
| const processingStatus = | |
| document.getElementById("processingStatus"); | |
| const steps = { | |
| starting: { | |
| step: 1, | |
| progress: 25, | |
| text: "Starting API comparison...", | |
| }, | |
| validating: { | |
| step: 1, | |
| progress: 50, | |
| text: "Validating inputs...", | |
| }, | |
| processing: { | |
| step: 2, | |
| progress: 60, | |
| text: "Processing request...", | |
| }, | |
| fetching: { | |
| step: 3, | |
| progress: 75, | |
| text: "Fetching API responses...", | |
| }, | |
| comparing: { | |
| step: 4, | |
| progress: 90, | |
| text: "Comparing results...", | |
| }, | |
| }; | |
| if (steps[status]) { | |
| const { step, progress, text } = steps[status]; | |
| progressBar.style.width = `${progress}%`; | |
| loadingText.textContent = text; | |
| processingStatus.textContent = text; | |
| // Update step indicators | |
| for (let i = 1; i <= 4; i++) { | |
| const stepEl = document.getElementById(`step${i}`); | |
| if (i <= step) { | |
| stepEl.classList.add("active"); | |
| } | |
| } | |
| // Update progress line | |
| document.getElementById("stepLineProgress").style.width = `${ | |
| (step - 1) * 33.33 | |
| }%`; | |
| } | |
| } | |
| }); | |
| // Validate JSON input | |
| function validateJson(input, fieldName) { | |
| try { | |
| if (input.value.trim()) { | |
| JSON.parse(input.value); | |
| } | |
| input.style.borderColor = ""; | |
| return true; | |
| } catch (e) { | |
| input.style.borderColor = "red"; | |
| alert(`Invalid JSON in ${fieldName}`); | |
| return false; | |
| } | |
| } | |
| // Add JSON validation to payload and headers fields | |
| ["api1_payload", "api2_payload", "api1_headers", "api2_headers"].forEach( | |
| (id) => { | |
| const element = document.getElementById(id); | |
| element.addEventListener("blur", () => validateJson(element, id)); | |
| } | |
| ); | |
| </script> | |
| <div | |
| style="text-align: center; margin-top: 20px; color: #666; font-size: 12px" | |
| > | |
| Copyright © 2025 TELUS Digital. All rights reserved.<br /> | |
| For internal use only. | |
| </div> | |
| </body> | |
| </html> | |