From d87f7741a4758518bd12b816410a11617195ca89 Mon Sep 17 00:00:00 2001 From: dheerajoruganty Date: Fri, 9 May 2025 22:55:01 +0000 Subject: [PATCH] feat: Add server feature through UI --- registry/templates/index.html | 1819 ++++++++++++++++++++++++++------- 1 file changed, 1470 insertions(+), 349 deletions(-) diff --git a/registry/templates/index.html b/registry/templates/index.html index 18905a8..e0edc29 100644 --- a/registry/templates/index.html +++ b/registry/templates/index.html @@ -19,85 +19,85 @@ /* -- Theme Color Variables -- Nova-Inspired Light Mode */ :root { - --bg-color: #f8f9fa; /* Very light gray/off-white */ - --text-color: #16191f; /* Very dark gray/near black */ - --card-bg: #ffffff; /* White cards */ - --card-border: #e0e0e0; /* Lighter gray border */ - --header-bg: #ffffff; /* White header */ - --sidebar-bg: var(--bg-color); /* Sidebar matches main background */ - --accent-color: #7a00cc; /* Purple accent */ - --accent-light-bg: #f7f5ff; /* Very light purple background */ - --button-bg: var(--accent-color); /* Purple button */ - --button-text: #ffffff; - --secondary-button-bg: #e9ecef; /* Light gray secondary button */ - --secondary-button-text: var(--text-color); - --link-color: var(--accent-color); /* Purple links */ - --badge-bg: #e9ecef; /* Light gray badge */ - --badge-text: var(--text-color); - --official-badge-bg: var(--accent-light-bg); /* Light purple badge */ - --official-badge-text: var(--accent-color); /* Purple text on badge */ - --input-bg: #ffffff; - --input-text: var(--text-color); - --input-placeholder: #6c757d; - --input-border: #ced4da; - --input-border-focus: var(--accent-color); /* Purple focus border */ + --bg-color: #f8f9fa !important; /* Very light gray/off-white */ + --text-color: #16191f !important; /* Very dark gray/near black */ + --card-bg: #ffffff !important; /* White cards */ + --card-border: #e0e0e0 !important; /* Lighter gray border */ + --header-bg: #ffffff !important; /* White header */ + --sidebar-bg: var(--bg-color) !important; /* Sidebar matches main background */ + --accent-color: #7a00cc !important; /* Purple accent */ + --accent-light-bg: #f7f5ff !important; /* Very light purple background */ + --button-bg: var(--accent-color) !important; /* Purple button */ + --button-text: #ffffff !important; + --secondary-button-bg: #e9ecef !important; /* Light gray secondary button */ + --secondary-button-text: var(--text-color) !important; + --link-color: var(--accent-color) !important; /* Purple links */ + --badge-bg: #e9ecef !important; /* Light gray badge */ + --badge-text: var(--text-color) !important; + --official-badge-bg: var(--accent-light-bg) !important; /* Light purple badge */ + --official-badge-text: var(--accent-color) !important; /* Purple text on badge */ + --input-bg: #ffffff !important; + --input-text: var(--text-color) !important; + --input-placeholder: #6c757d !important; + --input-border: #ced4da !important; + --input-border-focus: var(--accent-color) !important; /* Purple focus border */ /* Theme Toggle (Keep consistent or adapt?) */ - --toggle-button-bg: var(--input-bg); - --toggle-button-text: var(--text-color); - --toggle-button-border: var(--input-border); + --toggle-button-bg: var(--input-bg) !important; + --toggle-button-text: var(--text-color) !important; + --toggle-button-border: var(--input-border) !important; /* Sidebar Toggle (Keep consistent or adapt?) */ - --sidebar-toggle-bg-light: none; - --sidebar-toggle-text-light: var(--text-color); - --sidebar-toggle-border-light: none; + --sidebar-toggle-bg-light: none !important; + --sidebar-toggle-text-light: var(--text-color) !important; + --sidebar-toggle-border-light: none !important; } /* Dark mode would also need updating to match */ html.dark-mode { /* TODO: Define Nova-inspired dark theme variables */ - --bg-color: #212529; /* Placeholder dark */ - --text-color: #f8f9fa; /* Placeholder light text */ - --card-bg: #343a40; /* Placeholder dark card */ - --card-border: #495057; - --header-bg: #212529; /* Dark header */ - --accent-color: #a040ff; /* Lighter purple for dark */ - --accent-light-bg: #3a304f; /* Darker purple bg */ - --button-bg: var(--accent-color); - --button-text: #ffffff; - --secondary-button-bg: #495057; /* Dark gray secondary */ - --secondary-button-text: var(--text-color); - --link-color: var(--accent-color); - --badge-bg: #495057; - --badge-text: var(--text-color); - --official-badge-bg: var(--accent-light-bg); - --official-badge-text: var(--accent-color); - --input-bg: #343a40; - --input-text: var(--text-color); - --input-placeholder: #adb5bd; - --input-border: #495057; - --input-border-focus: var(--accent-color); + --bg-color: #212529 !important; /* Placeholder dark */ + --text-color: #f8f9fa !important; /* Placeholder light text */ + --card-bg: #343a40 !important; /* Placeholder dark card */ + --card-border: #495057 !important; + --header-bg: #212529 !important; /* Dark header */ + --accent-color: #a040ff !important; /* Lighter purple for dark */ + --accent-light-bg: #3a304f !important; /* Darker purple bg */ + --button-bg: var(--accent-color) !important; + --button-text: #ffffff !important; + --secondary-button-bg: #495057 !important; /* Dark gray secondary */ + --secondary-button-text: var(--text-color) !important; + --link-color: var(--accent-color) !important; + --badge-bg: #495057 !important; + --badge-text: var(--text-color) !important; + --official-badge-bg: var(--accent-light-bg) !important; + --official-badge-text: var(--accent-color) !important; + --input-bg: #343a40 !important; + --input-text: var(--text-color) !important; + --input-placeholder: #adb5bd !important; + --input-border: #495057 !important; + --input-border-focus: var(--accent-color) !important; /* Keep toggles simple for now */ - --toggle-button-bg: #495057; - --toggle-button-text: var(--text-color); - --toggle-button-border: #6c757d; + --toggle-button-bg: #495057 !important; + --toggle-button-text: var(--text-color) !important; + --toggle-button-border: #6c757d !important; } - /* Apply variables */ + /* Apply variables with !important to override style.css */ body { - background-color: var(--bg-color); - color: var(--text-color); + background-color: var(--bg-color) !important; + color: var(--text-color) !important; font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; /* Improve readability */ } .main-header { - background-color: var(--header-bg); - border-bottom: 1px solid var(--card-border); /* Add subtle border */ + background-color: var(--header-bg) !important; + border-bottom: 1px solid var(--card-border) !important; /* Add box-shadow for slight elevation */ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); /* Assuming header text color is inherited or set in style.css */ } .sidebar { - background-color: var(--sidebar-bg); - color: var(--text-color); /* Ensure sidebar uses main text color */ + background-color: var(--sidebar-bg) !important; + color: var(--text-color) !important; /* Ensure sidebar uses main text color */ } .sidebar h3 { color: var(--text-color); /* Ensure heading uses text color */ @@ -116,14 +116,14 @@ /* Assuming the count badge has its own styling */ } .service-card { - background-color: var(--card-bg); - border: 1px solid var(--card-border); - color: var(--text-color); /* Ensure card text uses theme color */ + background-color: var(--card-bg) !important; + border: 1px solid var(--card-border) !important; + color: var(--text-color) !important; /* Ensure card text uses theme color */ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); /* Subtle shadow */ border-radius: 12px; /* --- Add rounding --- */ } .service-card h2, .service-card .owner, .service-card .description { - color: var(--text-color); /* Explicitly set text color */ + color: var(--text-color) !important; /* Explicitly set text color */ } /* Example for a specific badge if needed */ .badge, @@ -141,8 +141,8 @@ /* Assuming style.css defines button styles, override if needed */ /* background-color: var(--button-bg); */ /* color: var(--button-text); */ - background-color: var(--button-bg); - color: var(--button-text); + background-color: var(--button-bg) !important; + color: var(--button-text) !important; border: none; padding: 8px 16px; border-radius: 8px; /* --- Add rounding --- */ @@ -161,8 +161,8 @@ } .logout-button, .search-bar button { - background-color: var(--secondary-button-bg); - color: var(--secondary-button-text); + background-color: var(--secondary-button-bg) !important; + color: var(--secondary-button-text) !important; border: 1px solid var(--card-border); /* Use card border for light gray */ padding: 8px 16px; border-radius: 8px; /* --- Add rounding --- */ @@ -300,7 +300,7 @@ /* Ensure main title uses text color */ .content h1 { - color: var(--text-color); + color: var(--text-color) !important; } /* --- Theme Toggle Button --- */ @@ -546,25 +546,215 @@ align-items: center; /* Vertically center items inside logo */ gap: 8px; /* Space between logo image and text */ } + + /* Registration Modal Tabs */ + .registration-tabs { + display: flex; + margin-bottom: 15px; + border-bottom: 1px solid var(--card-border); + } + .tab-button { + padding: 10px 15px; + cursor: pointer; + border: none; + background-color: transparent; + color: var(--text-color); + font-weight: 600; + border-bottom: 3px solid transparent; + margin-bottom: -1px; /* Overlap with container border */ + } + .tab-button.active { + border-bottom-color: var(--accent-color); + color: var(--accent-color); + } + .tab-button:hover { + background-color: var(--secondary-button-bg); + } + .tab-content { + display: none; + } + .tab-content.active { + display: block; + } + .form-input { + width: 100%; + padding: 8px 10px; + margin-bottom: 10px; + border: 1px solid var(--input-border); + border-radius: 6px; + background-color: var(--input-bg); + color: var(--input-text); + box-sizing: border-box; + } + .form-input:focus { + border-color: var(--input-border-focus); + outline: none; + box-shadow: 0 0 0 2px rgba(122, 0, 204, 0.2); + } + .form-group { + margin-bottom: 15px; + } + .form-group label { + display: block; + margin-bottom: 5px; + font-weight: 600; + color: var(--text-color); + } + #registration-feedback.success { + background-color: #d4edda; /* Light green */ + color: #155724; /* Dark green */ + border: 1px solid #c3e6cb; + } + #registration-feedback.error { + background-color: #f8d7da; /* Light red */ + color: #721c24; /* Dark red */ + border: 1px solid #f5c6cb; + } + + /* --- Register Server Button --- */ + .register-server-button { + background-color: var(--accent-color); + color: var(--button-text); + border: none; + padding: 8px 16px; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s ease; + margin-left: 10px; /* Space from previous element */ + } + .register-server-button:hover { + opacity: 0.9; + } + + /* Registration Modal Styles */ + /* (Reusing .modal-overlay and .modal-content from tool modal for consistency) */ + /* Additional styles for registration modal specifics if needed */ + #register-server-modal .modal-content { + min-width: 400px; /* Adjust as needed */ + max-width: 700px; /* Adjust as needed */ + } + + /* Registration Modal Tabs */ + .registration-tabs { + display: flex; + margin-bottom: 15px; + border-bottom: 1px solid var(--card-border); + } + .tab-button { + padding: 10px 15px; + cursor: pointer; + border: none; + background-color: transparent; + color: var(--text-color); + font-weight: 600; + border-bottom: 3px solid transparent; + margin-bottom: -1px; /* Overlap with container border */ + } + .tab-button.active { + border-bottom-color: var(--accent-color); + color: var(--accent-color); + } + .tab-button:hover { + background-color: var(--secondary-button-bg); + } + .tab-content { + display: none; + } + .tab-content.active { + display: block; + } + .form-input { + width: 100%; + padding: 8px 10px; + margin-bottom: 10px; + border: 1px solid var(--input-border); + border-radius: 6px; + background-color: var(--input-bg); + color: var(--input-text); + box-sizing: border-box; + } + .form-input:focus { + border-color: var(--input-border-focus); + outline: none; + box-shadow: 0 0 0 2px rgba(122, 0, 204, 0.2); + } + .form-group { + margin-bottom: 15px; + } + .form-group label { + display: block; + margin-bottom: 5px; + font-weight: 600; + color: var(--text-color); + } + #registration-feedback.success { + background-color: #d4edda; /* Light green */ + color: #155724; /* Dark green */ + border: 1px solid #c3e6cb; + } + #registration-feedback.error { + background-color: #f8d7da; /* Light red */ + color: #721c24; /* Dark red */ + border: 1px solid #f5c6cb; + } + + /* Add these additional style rules to explicitly override style.css colors */ + .sidebar-toggle-button { + color: var(--text-color) !important; + } + .theme-toggle-button { + color: var(--text-color) !important; + } + .logo span { + color: var(--text-color) !important; + } + .header-right .user-display { + color: var(--text-color) !important; + } + .sidebar ul li span:first-child { + color: var(--text-color) !important; + } + .card-header h2 { + color: var(--text-color) !important; + } + .card-body .owner { + color: var(--text-color) !important; + } + .badge { + color: var(--text-color) !important; + background-color: var(--badge-bg) !important; + } + .search-bar input[type="search"] { + background-color: var(--input-bg) !important; + color: var(--input-text) !important; + border-color: var(--input-border) !important; + } + .modal-content { + background-color: var(--card-bg) !important; + color: var(--text-color) !important; + } @@ -1361,6 +2152,9 @@
+ {% if username %} + + {% endif %} User: {{ username }}
@@ -1505,11 +2299,12 @@

{{ service.display_name }}

{# Add ID to form if needed, but action URL is sufficient #} {# Add ID to label span #} @@ -1529,13 +2324,13 @@

{{ service.display_name }}

{# Use the consistent safe_path variable defined earlier #} {% set num_tools_id = 'num-tools-' + safe_path %} - +
{% if service.is_python %}🐍 Python{% endif %} ⚖️ {{ service.license }} - 🐧 🪟 + 🐧 🪟
@@ -1561,16 +2356,342 @@

Tools for Service

+ + + + console.log("DOM fully loaded and parsed."); + + // ===================================================================== + // == Event Handlers + // ===================================================================== + + // Handle toggle click + async function handleToggleClick(event) { + const checkboxElement = event.target; + const servicePath = checkboxElement.dataset.path; + + if (!servicePath) { + console.error("Missing data-path attribute on toggle checkbox"); + return; + } + + event.preventDefault(); // Prevent default form submission + console.log(`Toggle clicked for path: ${servicePath}`); + + const safePath = servicePath.replace(/^\//, '').replace(/\//g, '_').replace(/:/g, '_'); + const spinnerId = 'spinner-for-' + safePath; + const spinner = document.getElementById(spinnerId); + const form = checkboxElement.form; + const isEnabling = checkboxElement.checked; // The state the user wants to set + + // Show spinner and disable checkbox immediately for feedback + if (spinner) { + spinner.style.display = 'inline-block'; + } else { + console.warn(`Spinner element not found for ID: ${spinnerId}`); + } + checkboxElement.disabled = true; + + // Prepare form data + const formData = new FormData(); + if (isEnabling) { + formData.append('enabled', 'on'); + } + + try { + const response = await fetch(form.action, { + method: 'POST', + body: new URLSearchParams(formData) // Send as x-www-form-urlencoded + }); - - - \ No newline at end of file + const responseData = await response.json(); // Always try to parse JSON + + if (!response.ok) { + // Log error from backend response if possible + const errorMsg = responseData.detail || `HTTP error ${response.status}`; + console.error(`Error toggling service ${servicePath}: ${errorMsg}`); + alert(`Error toggling service: ${errorMsg}`); + } else { + console.log(`Toggle request successful for ${servicePath}. Backend response:`, responseData); + } + + } catch (error) { + console.error(`Network or fetch error toggling service ${servicePath}:`, error); + alert(`Failed to send toggle request: ${error}`); + } + } + + // Show tool modal + async function showToolModal(servicePath, serviceName) { + const toolModal = document.getElementById('tool-modal'); + const toolModalTitle = document.getElementById('tool-modal-title'); + const toolModalListContainer = document.getElementById('tool-modal-list-container'); + + if (!toolModal || !toolModalTitle || !toolModalListContainer) { + console.error("Tool modal elements not found!"); return; + } + + toolModalTitle.textContent = `Tools for ${serviceName}`; + toolModalListContainer.innerHTML = '

Loading...

'; + toolModal.style.display = 'flex'; + + // Ensure the path is properly formatted with leading slash + const apiPath = servicePath.startsWith('/') ? servicePath : '/' + servicePath; + + // Explicitly construct the full URL through the gateway/proxy, NOT direct to port 7860 + const proxyUrl = window.location.origin + '/api/tools' + apiPath; + console.log(`Fetching tools from: ${proxyUrl}`); + + // Use fetch with proper error handling + fetch(proxyUrl, { + method: 'GET', + credentials: 'same-origin' + }) + .then(response => { + console.log(`Tool fetch response: ${response.status} ${response.statusText}`); + if (!response.ok) { + return response.json().then(data => { + throw new Error(data.detail || `Error: ${response.status} ${response.statusText}`); + }).catch(e => { + throw new Error(`Error: ${response.status} ${response.statusText}`); + }); + } + return response.json(); + }) + .then(data => { + console.log(`Tool data received:`, data); + toolModalListContainer.innerHTML = ''; + + const tools = data.tools || []; + + if (tools && tools.length > 0) { + tools.forEach(tool => { + const toolDiv = document.createElement('div'); + toolDiv.style.marginBottom = '15px'; + toolDiv.style.borderBottom = '1px solid var(--card-border)'; + toolDiv.style.paddingBottom = '10px'; + + const nameEl = document.createElement('h4'); + nameEl.textContent = tool.name || 'Unnamed Tool'; + nameEl.style.marginTop = '0'; nameEl.style.marginBottom = '5px'; + toolDiv.appendChild(nameEl); + + // Render Parsed Description + if (tool.parsed_description) { + const mainDescEl = document.createElement('p'); + mainDescEl.textContent = tool.parsed_description.main || 'No description available.'; + mainDescEl.style.whiteSpace = 'pre-wrap'; + toolDiv.appendChild(mainDescEl); + + const renderSection = (title, content) => { + if (!content) return; + const titleEl = document.createElement('strong'); + titleEl.textContent = title + ':'; + titleEl.style.display = 'block'; titleEl.style.marginTop = '8px'; + toolDiv.appendChild(titleEl); + const preEl = document.createElement('pre'); + preEl.textContent = content; + preEl.style.marginLeft = '10px'; preEl.style.marginTop = '3px'; + preEl.style.whiteSpace = 'pre-wrap'; preEl.style.fontSize = '0.9em'; + toolDiv.appendChild(preEl); + }; + renderSection('Args', tool.parsed_description.args); + renderSection('Returns', tool.parsed_description.returns); + renderSection('Raises', tool.parsed_description.raises); + } else if (tool.description) { // Fallback + const descEl = document.createElement('p'); + descEl.textContent = tool.description; + descEl.style.marginBottom = '8px'; + toolDiv.appendChild(descEl); + } + + // Render Schema + if (tool.schema && typeof tool.schema === 'object' && Object.keys(tool.schema).length > 0) { + const schemaContainer = document.createElement('div'); + schemaContainer.style.marginTop = '10px'; + schemaContainer.innerHTML = 'Input Schema:'; + + const properties = tool.schema.properties; + const required = tool.schema.required || []; + + if (properties && typeof properties === 'object' && Object.keys(properties).length > 0) { + const propsList = document.createElement('div'); + propsList.style.marginLeft = '15px'; propsList.style.marginTop = '5px'; + propsList.style.borderLeft = '2px solid var(--card-border)'; propsList.style.paddingLeft = '10px'; + + for (const [propName, propDetails] of Object.entries(properties)) { + const propDiv = document.createElement('div'); + propDiv.style.marginBottom = '8px'; + + const nameSpan = document.createElement('strong'); + nameSpan.textContent = propName; + propDiv.appendChild(nameSpan); + + if (required.includes(propName)) { + const reqSpan = document.createElement('span'); + reqSpan.textContent = ' (required)'; reqSpan.style.color = '#dc3545'; + reqSpan.style.fontSize = '0.8em'; reqSpan.style.marginLeft = '3px'; + propDiv.appendChild(reqSpan); + } + if (propDetails.type) { + const typeSpan = document.createElement('span'); + typeSpan.textContent = ` - Type: ${propDetails.type}`; + typeSpan.style.color = 'var(--input-placeholder)'; typeSpan.style.marginLeft = '5px'; + propDiv.appendChild(typeSpan); + } + if (propDetails.description) { + const descP = document.createElement('p'); + descP.textContent = propDetails.description; + descP.style.margin = '3px 0 0 10px'; descP.style.fontSize = '0.85em'; + propDiv.appendChild(descP); + } + if (propDetails.default !== undefined) { + const defaultP = document.createElement('p'); + defaultP.textContent = `Default: ${JSON.stringify(propDetails.default)}`; + defaultP.style.margin = '3px 0 0 10px'; defaultP.style.fontSize = '0.85em'; + defaultP.style.color = 'var(--input-placeholder)'; + propDiv.appendChild(defaultP); + } + propsList.appendChild(propDiv); + } + schemaContainer.appendChild(propsList); + } else { + schemaContainer.innerHTML += '

No input parameters defined.

'; + } + // Optionally display $defs notice + if (tool.schema.$defs && Object.keys(tool.schema.$defs).length > 0) { + schemaContainer.innerHTML += '

Definitions: (Schema uses shared definitions not fully displayed here)

'; + } + toolDiv.appendChild(schemaContainer); + } else if (tool.schema && Object.keys(tool.schema).length === 0){ + toolDiv.innerHTML += '

Input Schema: No parameters defined.

'; + } + toolModalListContainer.appendChild(toolDiv); + }); + } else { + toolModalListContainer.innerHTML = ` +

No tools listed for this service.

+

+ Service reports ${data.tools ? data.tools.length : 0} tools. +
API path: ${apiPath} +
Last response: ${new Date().toLocaleTimeString()} +

+ `; + } + }) + .catch(error => { + console.error("Failed to fetch or display tools:", error); + toolModalListContainer.innerHTML = `

Could not load tools: ${error.message}

`; + }); + } + + // ===================================================================== + // == Core UI Update Functions + // ===================================================================== + + function formatTimeAgoJS(isoString) { + if (!isoString) return "Never"; + try { + const dt = new Date(isoString); + if (isNaN(dt.getTime())) { + console.error(`[formatTimeAgoJS] Invalid Date parsed from: ${isoString}`); + return "Invalid date"; + } + const now = new Date(); + const diff = now.getTime() - dt.getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 2) return "Just now"; + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`;