mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-11 08:28:11 -05:00
Compare commits
4 Commits
docs-py-sd
...
ui-instruc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f78b88bf01 | ||
|
|
0b7071ebfd | ||
|
|
750c64bc03 | ||
|
|
63ed2ea4af |
33
internal/server/static/authServices.html
Normal file
33
internal/server/static/authServices.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AuthServices View</title>
|
||||
<link rel="stylesheet" href="/ui/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar-container" data-active-nav="/ui/authServices"></div>
|
||||
|
||||
<aside class="second-nav">
|
||||
<h4>My Auth Services</h4>
|
||||
<div id="secondary-panel-content">
|
||||
<p>Fetching Auth Services...</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="main-content-container"></div>
|
||||
|
||||
<script type="module" src="/ui/js/authServices.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'auth-service-info-area', getAuthServiceInstructions())
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -174,7 +174,7 @@ body {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.tool-button {
|
||||
.second-nav-choice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
@@ -736,3 +736,56 @@ body {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-details-box {
|
||||
margin-top: 20px;
|
||||
overflow-x: auto;
|
||||
|
||||
h5 {
|
||||
color: var(--toolbox-blue);
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.item-details-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.item-detail-entry {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(120px, auto) 1fr;
|
||||
gap: 6px;
|
||||
padding: 4px 4px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
align-items: start;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item-detail-key {
|
||||
font-weight: bolder;
|
||||
color: var(--toolbox-blue);
|
||||
padding: 4px 6px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.item-detail-value {
|
||||
color: var(--text-primary-gray);
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.item-details-box.error p {
|
||||
color: #d93025;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
27
internal/server/static/js/authServices.js
Normal file
27
internal/server/static/js/authServices.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 { loadItems } from './resourceDisplay.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const authServiceListArea = document.getElementById('secondary-panel-content');
|
||||
const authServiceInfoArea = document.getElementById('auth-service-info-area');
|
||||
|
||||
if (!authServiceListArea || !authServiceInfoArea) {
|
||||
console.error('Required DOM elements not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
loadItems('authservices', authServiceListArea, authServiceInfoArea);
|
||||
});
|
||||
@@ -67,7 +67,7 @@ function renderToolList(apiResponse, secondNavContent, toolDisplayArea) {
|
||||
const button = document.createElement('button');
|
||||
button.textContent = toolName;
|
||||
button.dataset.toolname = toolName;
|
||||
button.classList.add('tool-button');
|
||||
button.classList.add('second-nav-choice');
|
||||
button.addEventListener('click', (event) => handleToolClick(event, secondNavContent, toolDisplayArea));
|
||||
li.appendChild(button);
|
||||
ul.appendChild(li);
|
||||
@@ -84,7 +84,7 @@ function renderToolList(apiResponse, secondNavContent, toolDisplayArea) {
|
||||
function handleToolClick(event, secondNavContent, toolDisplayArea) {
|
||||
const toolName = event.target.dataset.toolname;
|
||||
if (toolName) {
|
||||
const currentActive = secondNavContent.querySelector('.tool-button.active');
|
||||
const currentActive = secondNavContent.querySelector('.second-nav-choice.active');
|
||||
if (currentActive) {
|
||||
currentActive.classList.remove('active');
|
||||
}
|
||||
|
||||
@@ -48,6 +48,44 @@ function getHomepageInstructions() {
|
||||
`;
|
||||
}
|
||||
|
||||
function getSourceInstructions() {
|
||||
return `
|
||||
<div class="resource-instructions">
|
||||
<h1 class="resource-title">Sources</h1>
|
||||
<p class="resource-intro">To inspect a specific source, please click on one of your sources to the left.</p>
|
||||
|
||||
<h2 class="resource-subtitle">What are Sources?</h2>
|
||||
<p class="resource-description">
|
||||
Sources represent your different data sources that a tool can interact with. You can define Sources as a map in the <code>sources</code> section of your <code>tools.yaml</code> file.
|
||||
Typically, a source configuration will contain any information needed to connect with and interact with the database.
|
||||
</p>
|
||||
|
||||
<a href="https://googleapis.github.io/genai-toolbox/resources/sources/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Sources Documentation</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function getAuthServiceInstructions() {
|
||||
return `
|
||||
<div class="resource-instructions">
|
||||
<h1 class="resource-title">Auth Services</h1>
|
||||
<p class="resource-intro">To inspect Auth Services, please click on one of your services to the left.</p>
|
||||
|
||||
<h2 class="resource-subtitle">What are Auth Services?</h2>
|
||||
<p class="resource-description">
|
||||
AuthServices represent services that handle authentication and authorization. It can primarily be used by Tools in two different ways:
|
||||
<ul>
|
||||
<li><strong>Authorized Invocation:</strong> A tool is validated by the auth service before the call can be invoked. Toolbox will reject any calls that fail to validate.</li>
|
||||
<li><strong>Authenticated Parameters:</strong> A parameter thats value must be replaced with a field from an OIDC claim.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p class="resource-description">You can define Auth Services as a map in the <code>authServices</code> section of your <code>tools.yaml</code> file. <br><br></p>
|
||||
|
||||
<a href="https://googleapis.github.io/genai-toolbox/resources/authservices/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">AuthServices Documentation</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function getToolInstructions() {
|
||||
return `
|
||||
<div class="resource-instructions">
|
||||
|
||||
@@ -30,8 +30,8 @@ function renderNavbar(containerId, activePath) {
|
||||
<img src="/ui/assets/mcptoolboxlogo.png" alt="App Logo">
|
||||
</div>
|
||||
<ul>
|
||||
<!--<li><a href="/ui/sources">Sources</a></li>-->
|
||||
<!--<li><a href="/ui/authservices">Auth Services</a></li>-->
|
||||
<li><a href="/ui/sources">Sources</a></li>
|
||||
<li><a href="/ui/authservices">Auth Services</a></li>
|
||||
<li><a href="/ui/tools">Tools</a></li>
|
||||
<li><a href="/ui/toolsets">Toolsets</a></li>
|
||||
</ul>
|
||||
|
||||
271
internal/server/static/js/resourceDisplay.js
Normal file
271
internal/server/static/js/resourceDisplay.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// 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.
|
||||
|
||||
const MOCK_SOURCE_MANIFEST = {
|
||||
"sources": {
|
||||
"my-alloydb-pg-source": {
|
||||
kind: "alloydb-postgres",
|
||||
project: "my-project-id",
|
||||
region: "us-central1",
|
||||
cluster: "my-cluster",
|
||||
instance: "my-instance",
|
||||
database: "my_db",
|
||||
ipType: "public"
|
||||
},
|
||||
"my-bigtable-source": {
|
||||
kind: "bigtable",
|
||||
project: "my-project-id",
|
||||
instance: "test-instance"
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
async function getMockSourceListData() {
|
||||
console.warn(`[MOCK] Returning mock data for all sources`);
|
||||
return MOCK_SOURCE_MANIFEST;
|
||||
}
|
||||
|
||||
async function getMockSourceDetailsData(sourceName) {
|
||||
console.warn(`[MOCK] Returning mock data for source details: ${sourceName}`);
|
||||
const SOURCE_DATA = MOCK_SOURCE_MANIFEST.sources[sourceName];
|
||||
if (SOURCE_DATA) {
|
||||
return { "sources": { [sourceName]: SOURCE_DATA } };
|
||||
} else {
|
||||
throw new Error(`Mock Source "${sourceName}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
const MOCK_AUTH_SERVICE_MANIFEST = {
|
||||
"authservices": {
|
||||
"my-auth-service-1": {
|
||||
kind: "google oauth2",
|
||||
},
|
||||
"another-auth": {
|
||||
kind: "other",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function getMockAuthServiceListData() {
|
||||
console.warn(`[MOCK] Returning mock data for all authservices`);
|
||||
return MOCK_AUTH_SERVICE_MANIFEST;
|
||||
}
|
||||
|
||||
async function getMockAuthServiceDetailsData(authServiceName) {
|
||||
console.warn(`[MOCK] Returning mock data for auth service details: ${authServiceName}`);
|
||||
const SERVICE_DATA = MOCK_AUTH_SERVICE_MANIFEST.authservices[authServiceName];
|
||||
if (SERVICE_DATA) {
|
||||
return { "authservices": { [authServiceName]: SERVICE_DATA } };
|
||||
} else {
|
||||
throw new Error(`Mock Auth Service "${authServiceName}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
const itemConfigs = {
|
||||
sources: {
|
||||
getListData: getMockSourceListData, // replace with real API call to GET all sources
|
||||
getDetailsData: getMockSourceDetailsData, // replace with real API call to GET specific source
|
||||
listKey: 'sources',
|
||||
nameAttribute: 'data-itemname',
|
||||
displayName: 'Source',
|
||||
singularName: 'source',
|
||||
},
|
||||
authservices: {
|
||||
getListData: getMockAuthServiceListData, // replace with real API call to GET all auth services
|
||||
getDetailsData: getMockAuthServiceDetailsData, //replace with real API call to get specific auth service
|
||||
listKey: 'authservices',
|
||||
nameAttribute: 'data-itemname',
|
||||
displayName: 'Auth Service',
|
||||
singularName: 'authservice',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the details of an item.
|
||||
* @param {string} itemName - The name of the item.
|
||||
* @param {!Object<string, *>} itemDetails - The object containing the item's attributes.
|
||||
* @param {!HTMLElement} displayArea - The HTML element to render the details into.
|
||||
* @param {string} displayName - The display name of the item type.
|
||||
*/
|
||||
function displayItemDetails(itemName, itemDetails, displayArea) {
|
||||
displayArea.innerHTML = '';
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'tool-box item-details-box';
|
||||
|
||||
const dl = document.createElement('dl');
|
||||
dl.classList.add('item-details-list');
|
||||
|
||||
// add the name (key in the original json response) as the first key-value pair
|
||||
const nameEntryDiv = document.createElement('div');
|
||||
nameEntryDiv.classList.add('item-detail-entry');
|
||||
|
||||
const nameDt = document.createElement('dt');
|
||||
nameDt.classList.add('item-detail-key');
|
||||
nameDt.textContent = 'name';
|
||||
nameEntryDiv.appendChild(nameDt);
|
||||
|
||||
const nameDd = document.createElement('dd');
|
||||
nameDd.classList.add('item-detail-value');
|
||||
nameDd.textContent = itemName;
|
||||
nameEntryDiv.appendChild(nameDd);
|
||||
|
||||
dl.appendChild(nameEntryDiv);
|
||||
|
||||
const entries = Object.entries(itemDetails);
|
||||
if (entries.length === 0 && !itemName) {
|
||||
const para = document.createElement('p');
|
||||
para.textContent = 'No details available.';
|
||||
container.appendChild(para);
|
||||
displayArea.appendChild(container);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.classList.add('item-detail-entry');
|
||||
|
||||
const dt = document.createElement('dt');
|
||||
dt.classList.add('item-detail-key');
|
||||
dt.textContent = key;
|
||||
entryDiv.appendChild(dt);
|
||||
|
||||
const dd = document.createElement('dd');
|
||||
dd.classList.add('item-detail-value');
|
||||
dd.textContent = String(value);
|
||||
entryDiv.appendChild(dd);
|
||||
|
||||
dl.appendChild(entryDiv);
|
||||
}
|
||||
container.appendChild(dl);
|
||||
displayArea.appendChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and displays details for a specific item.
|
||||
* @param {string} itemType - 'sources' or 'authservices'.
|
||||
* @param {string} itemName - The name of the item.
|
||||
* @param {!HTMLElement} displayArea - The element to render details into.
|
||||
* @param {!Object} config - The configuration object for the item type.
|
||||
*/
|
||||
async function fetchItemDetails(itemType, itemName, displayArea, config) {
|
||||
displayArea.innerHTML = `<p>Loading details for ${itemName}...</p>`;
|
||||
try {
|
||||
const apiResponse = await config.getDetailsData(itemName);
|
||||
const itemDetails = apiResponse && apiResponse[config.listKey] ? apiResponse[config.listKey][itemName] : null;
|
||||
|
||||
if (!itemDetails) {
|
||||
throw new Error(`${config.displayName} "${itemName}" data not found in API response.`);
|
||||
}
|
||||
|
||||
displayItemDetails(itemName, itemDetails, displayArea);
|
||||
console.log(`${config.displayName} details:`, itemDetails);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to load details for ${config.singularName} "${itemName}":`, error);
|
||||
displayArea.innerHTML = `<p class="error">Failed to load details for ${itemName}: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the click event on an item button.
|
||||
* @param {!Event} event - The click event object.
|
||||
* @param {string} itemType - 'sources' or 'authservices'.
|
||||
* @param {!HTMLElement} listContainer - The parent element containing the item buttons.
|
||||
* @param {!HTMLElement} displayArea - The HTML element where item details will be shown.
|
||||
* @param {!Object} config - The configuration object for the item type.
|
||||
*/
|
||||
function handleItemClick(event, itemType, listContainer, displayArea, config) {
|
||||
const itemName = event.target.getAttribute(config.nameAttribute);
|
||||
if (itemName) {
|
||||
const currentActive = listContainer.querySelector('.second-nav-choice.active');
|
||||
if (currentActive) currentActive.classList.remove('active');
|
||||
event.target.classList.add('active');
|
||||
fetchItemDetails(itemType, itemName, displayArea, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the list of items as buttons.
|
||||
* @param {string} itemType - 'sources' or 'authservices'.
|
||||
* @param {?Object} apiResponse - The API response object.
|
||||
* @param {!HTMLElement} listContainer - The element to render the list into.
|
||||
* @param {!HTMLElement} displayArea - The element for displaying item details.
|
||||
* @param {!Object} config - The configuration object for the item type.
|
||||
*/
|
||||
function renderList(itemType, apiResponse, listContainer, displayArea, config) {
|
||||
listContainer.innerHTML = '';
|
||||
|
||||
const itemsObject = apiResponse ? apiResponse[config.listKey] : null;
|
||||
|
||||
if (!itemsObject || typeof itemsObject !== 'object' || itemsObject === null) {
|
||||
console.error(`Error: Expected an object with a "${config.listKey}" property, but received:`, apiResponse);
|
||||
listContainer.textContent = `Error: Invalid response format from ${config.displayName} API.`;
|
||||
return;
|
||||
}
|
||||
|
||||
const itemNames = Object.keys(itemsObject);
|
||||
|
||||
if (itemNames.length === 0) {
|
||||
listContainer.textContent = `No ${config.displayName}s found.`;
|
||||
return;
|
||||
}
|
||||
|
||||
const ul = document.createElement('ul');
|
||||
itemNames.forEach(itemName => {
|
||||
const li = document.createElement('li');
|
||||
const button = document.createElement('button');
|
||||
button.textContent = itemName;
|
||||
button.setAttribute(config.nameAttribute, itemName);
|
||||
button.classList.add('second-nav-choice');
|
||||
button.addEventListener('click', (event) => handleItemClick(event, itemType, listContainer, displayArea, config));
|
||||
li.appendChild(button);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
listContainer.appendChild(ul);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and renders a list of items (sources or authservices).
|
||||
* @param {string} itemType - 'sources' or 'authservices'.
|
||||
* @param {!HTMLElement} listContainer - Element to render the list into.
|
||||
* @param {!HTMLElement} displayArea - Element to display item details.
|
||||
* @returns {!Promise<void>}
|
||||
*/
|
||||
async function loadItems(itemType, listContainer, displayArea) {
|
||||
const config = itemConfigs[itemType];
|
||||
if (!config) {
|
||||
console.error(`Unknown item type: ${itemType}`);
|
||||
if (listContainer) {
|
||||
listContainer.innerHTML = `<p class="error">Configuration error.</p>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!listContainer || !displayArea) {
|
||||
console.error("List container or display area not provided to loadItems");
|
||||
return;
|
||||
}
|
||||
|
||||
listContainer.innerHTML = `<p>Fetching ${config.displayName}s...</p>`;
|
||||
try {
|
||||
const apiResponse = await config.getListData();
|
||||
renderList(itemType, apiResponse, listContainer, displayArea, config);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load ${config.displayName}s:`, error);
|
||||
listContainer.innerHTML = `<p class="error">Failed to load ${config.displayName}s: <pre><code>${error}</code></pre></p>`;
|
||||
}
|
||||
}
|
||||
|
||||
export { loadItems };
|
||||
27
internal/server/static/js/sources.js
Normal file
27
internal/server/static/js/sources.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 { loadItems } from './resourceDisplay.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sourceListArea = document.getElementById('secondary-panel-content');
|
||||
const sourceInfoArea = document.getElementById('source-info-area');
|
||||
|
||||
if (!sourceListArea || !sourceInfoArea) {
|
||||
console.error('Required DOM elements not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
loadItems('sources', sourceListArea, sourceInfoArea);
|
||||
});
|
||||
33
internal/server/static/sources.html
Normal file
33
internal/server/static/sources.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sources View</title>
|
||||
<link rel="stylesheet" href="/ui/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar-container" data-active-nav="/ui/sources"></div>
|
||||
|
||||
<aside class="second-nav">
|
||||
<h4>My Sources</h4>
|
||||
<div id="secondary-panel-content">
|
||||
<p>Fetching sources...</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="main-content-container"></div>
|
||||
|
||||
<script type="module" src="/ui/js/sources.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'source-info-area', getSourceInstructions())
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -22,6 +22,8 @@ func webRouter() (chi.Router, error) {
|
||||
|
||||
// direct routes for html pages to provide clean URLs
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/index.html") })
|
||||
r.Get("/sources", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/sources.html") })
|
||||
r.Get("/authservices", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/authServices.html") })
|
||||
r.Get("/tools", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/tools.html") })
|
||||
r.Get("/toolsets", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/toolsets.html") })
|
||||
|
||||
|
||||
@@ -73,6 +73,34 @@ func TestWebEndpoint(t *testing.T) {
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "Toolsets View",
|
||||
},
|
||||
{
|
||||
name: "web sources page",
|
||||
path: "/ui/sources",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "Sources View",
|
||||
},
|
||||
{
|
||||
name: "web sources page with trailing slash",
|
||||
path: "/ui/sources/",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "Sources View",
|
||||
},
|
||||
{
|
||||
name: "web auth services page",
|
||||
path: "/ui/authservices",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "AuthServices View",
|
||||
},
|
||||
{
|
||||
name: "web auth services page with trailing slash",
|
||||
path: "/ui/authservices/",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "AuthServices View",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
Reference in New Issue
Block a user