diff --git a/internal/server/static/css/style.css b/internal/server/static/css/style.css index e93421a8deeb..019c2a09f09a 100644 --- a/internal/server/static/css/style.css +++ b/internal/server/static/css/style.css @@ -93,6 +93,7 @@ body { .nav-logo { width: 90%; margin-bottom: 20px; + flex-shrink: 0; img { max-width: 100%; @@ -199,6 +200,11 @@ body { } #secondary-panel-content { + flex: 1; + overflow-y: auto; + width: 100%; + min-height: 0; + ul { list-style: none; padding: 0; @@ -519,3 +525,56 @@ body { font-family: monospace; } } + +.search-container { + display: flex; + width: 100%; + margin-bottom: 15px; + + #toolset-search-input { + flex-grow: 1; + padding: 10px 12px; + border: 1px solid #ccc; + border-radius: 20px 0 0 20px; + border-right: none; + font-family: inherit; + font-size: 0.9em; + color: var(--text-primary-gray); + + &:focus { + outline: none; + border-color: var(--toolbox-blue); + box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3); + } + + &::placeholder { + color: var(--text-secondary-gray); + } + } + + #toolset-search-button { + padding: 10px 15px; + border: 1px solid var(--button-primary); + background-color: var(--button-primary); + color: white; + border-radius: 0 20px 20px 0; + cursor: pointer; + font-family: inherit; + font-size: 0.9em; + font-weight: bold; + transition: opacity 0.2s ease-in-out; + flex-shrink: 0; + line-height: 1; + + &:hover { + opacity: 0.8; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3); + } + } +} + + diff --git a/internal/server/static/js/loadTools.js b/internal/server/static/js/loadTools.js index 3dd0fcb5aadb..d7c11a387eca 100644 --- a/internal/server/static/js/loadTools.js +++ b/internal/server/static/js/loadTools.js @@ -34,7 +34,7 @@ export async function loadTools(secondNavContent, toolDisplayArea, toolsetName) renderToolList(apiResponse, secondNavContent, toolDisplayArea); } catch (error) { console.error('Failed to load tools:', error); - secondNavContent.innerHTML = '
Failed to load tools. Please try again later.
'; + secondNavContent.innerHTML = `Failed to load tools:
${error}`;
}
}
diff --git a/internal/server/static/js/toolDisplay.js b/internal/server/static/js/toolDisplay.js
index b20ff1560dfa..8fe9f66a74ed 100644
--- a/internal/server/static/js/toolDisplay.js
+++ b/internal/server/static/js/toolDisplay.js
@@ -267,27 +267,37 @@ function createAuthTokenInfoDropdown() {
content.appendChild(tabButtons);
const tabContentContainer = document.createElement('div');
- const standardTemplate = document.getElementById('auth-token-standard-template');
- const standardAccount = document.importNode(standardTemplate.content, true).firstElementChild;
- const serviceTemplate = document.getElementById('auth-token-service-template');
- const serviceAccount = document.importNode(serviceTemplate.content, true).firstElementChild;
-
- tabContentContainer.appendChild(standardAccount);
- tabContentContainer.appendChild(serviceAccount);
+ const standardAccInstructions = document.createElement('div');
+ const serviceAccInstructions = document.createElement('div');
+
+ standardAccInstructions.id = 'auth-tab-standard';
+ standardAccInstructions.className = 'auth-tab-content active';
+ standardAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_STANDARD;
+ serviceAccInstructions.id = 'auth-tab-service';
+ serviceAccInstructions.className = 'auth-tab-content';
+ serviceAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT;
+
+ tabContentContainer.appendChild(standardAccInstructions);
+ tabContentContainer.appendChild(serviceAccInstructions);
content.appendChild(tabContentContainer);
// switching tabs logic
const tabBtns = [leftTab, rightTab];
+ const tabContents = [standardAccInstructions, serviceAccInstructions];
+
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
// deactivate all buttons and contents
tabBtns.forEach(b => b.classList.remove('active'));
- content.querySelectorAll('.auth-tab-content').forEach(c => c.classList.remove('active'));
+ tabContents.forEach(c => c.classList.remove('active'));
- // activate clicked button and corresponding content
btn.classList.add('active');
+
const tabId = btn.getAttribute('data-tab');
- content.querySelector(`#auth-tab-${tabId}`).classList.add('active');
+ const activeContent = content.querySelector(`#auth-tab-${tabId}`);
+ if (activeContent) {
+ activeContent.classList.add('active');
+ }
});
});
@@ -451,4 +461,50 @@ export function isParamIncluded(toolId, paramName) {
console.warn(`Include checkbox not found for ID: ${includeCheckboxId}`);
return null;
-}
\ No newline at end of file
+}
+
+// Templates for inserting token retrieval instructions into edit header modal
+const AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT = `
+ To obtain a Google OAuth ID token using a service account:
+gcloud auth list
+ gcloud auth print-identity-token --audiences=YOUR_CLIENT_ID_HERE
+ _token
+ {
+ "Content-Type": "application/json",
+ "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
+}
+ This token is typically short-lived.
`; + +const AUTH_TOKEN_INSTRUCTIONS_STANDARD = ` +To obtain a Google OAuth ID token using a standard account:
+gcloud auth list
+ https://developers.google.com/oauthplayground
+ _token
+ {
+ "Content-Type": "application/json",
+ "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
+}
+ This token is typically short-lived.
`; \ No newline at end of file diff --git a/internal/server/static/js/toolsets.js b/internal/server/static/js/toolsets.js new file mode 100644 index 000000000000..f49afbf5f3ef --- /dev/null +++ b/internal/server/static/js/toolsets.js @@ -0,0 +1,51 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { loadTools } from "./loadTools.js"; + +document.addEventListener('DOMContentLoaded', () => { + const searchInput = document.getElementById('toolset-search-input'); + const searchButton = document.getElementById('toolset-search-button'); + const secondNavContent = document.getElementById('secondary-panel-content'); + const toolDisplayArea = document.getElementById('tool-display-area'); + + if (!searchInput || !searchButton || !secondNavContent || !toolDisplayArea) { + console.error('Required DOM elements not found.'); + return; + } + + // Event listener for search button click + searchButton.addEventListener('click', () => { + toolDisplayArea.innerHTML = ''; + const toolsetName = searchInput.value.trim(); + if (toolsetName) { + loadTools(secondNavContent, toolDisplayArea, toolsetName) + } else { + secondNavContent.innerHTML = 'Please enter a toolset name to see available tools.
To view the default toolset that consists of all tools, please select the "Tools" tab.
Please enter a toolset name to see available tools.
To view the default toolset that consists of all tools, please select the "Tools" tab.