feat(api): Add custom auth input component for operation pages
- Create api-auth-input.ts TypeScript component - Store credentials in sessionStorage (secure, cleared on tab close) - Register component in main.js - Add auth form styling with dark theme support - Add component wrapper to rapidoc-mini.html template - Fix SCSS dark theme (use CSS selectors, not non-existent mixin)claude/api-code-samples-plan-MEkQO
parent
4ee1311e37
commit
1c8a2d3fa0
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* API Auth Input Component
|
||||
*
|
||||
* Provides credential input fields for API operations.
|
||||
* Stores credentials in sessionStorage for "Try it" requests.
|
||||
*/
|
||||
|
||||
interface ComponentOptions {
|
||||
component: HTMLElement;
|
||||
}
|
||||
|
||||
interface AuthCredentials {
|
||||
bearer?: string;
|
||||
basic?: { username: string; password: string };
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'influxdb_api_credentials';
|
||||
|
||||
/**
|
||||
* Get stored credentials from sessionStorage
|
||||
*/
|
||||
function getCredentials(): AuthCredentials {
|
||||
try {
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store credentials in sessionStorage
|
||||
*/
|
||||
function setCredentials(credentials: AuthCredentials): void {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(credentials));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the auth input form
|
||||
*/
|
||||
function createAuthForm(schemes: string[]): HTMLElement {
|
||||
const form = document.createElement('div');
|
||||
form.className = 'api-auth-form';
|
||||
form.innerHTML = `
|
||||
<h4>Authentication</h4>
|
||||
<p class="auth-form-description">Enter credentials to use with "Try it" requests.</p>
|
||||
${
|
||||
schemes.includes('bearer')
|
||||
? `
|
||||
<div class="auth-field">
|
||||
<label for="auth-bearer">Bearer Token</label>
|
||||
<input type="password" id="auth-bearer" placeholder="Enter your API token" />
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
schemes.includes('basic')
|
||||
? `
|
||||
<div class="auth-field">
|
||||
<label for="auth-username">Username</label>
|
||||
<input type="text" id="auth-username" placeholder="Username (optional)" />
|
||||
</div>
|
||||
<div class="auth-field">
|
||||
<label for="auth-password">Password / Token</label>
|
||||
<input type="password" id="auth-password" placeholder="Enter token" />
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
<button type="button" class="btn btn-primary auth-save">Save Credentials</button>
|
||||
`;
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the auth input component
|
||||
*/
|
||||
export default function ApiAuthInput({ component }: ComponentOptions): void {
|
||||
const schemesAttr = component.dataset.schemes || 'bearer';
|
||||
const schemes = schemesAttr.split(',').map((s) => s.trim().toLowerCase());
|
||||
|
||||
const form = createAuthForm(schemes);
|
||||
component.appendChild(form);
|
||||
|
||||
// Load existing credentials
|
||||
const credentials = getCredentials();
|
||||
const bearerInput = form.querySelector<HTMLInputElement>('#auth-bearer');
|
||||
const usernameInput = form.querySelector<HTMLInputElement>('#auth-username');
|
||||
const passwordInput = form.querySelector<HTMLInputElement>('#auth-password');
|
||||
|
||||
if (bearerInput && credentials.bearer) {
|
||||
bearerInput.value = credentials.bearer;
|
||||
}
|
||||
if (usernameInput && credentials.basic?.username) {
|
||||
usernameInput.value = credentials.basic.username;
|
||||
}
|
||||
if (passwordInput && credentials.basic?.password) {
|
||||
passwordInput.value = credentials.basic.password;
|
||||
}
|
||||
|
||||
// Save button handler
|
||||
const saveBtn = form.querySelector('.auth-save');
|
||||
saveBtn?.addEventListener('click', () => {
|
||||
const newCredentials: AuthCredentials = {};
|
||||
|
||||
if (bearerInput?.value) {
|
||||
newCredentials.bearer = bearerInput.value;
|
||||
}
|
||||
if (usernameInput?.value || passwordInput?.value) {
|
||||
newCredentials.basic = {
|
||||
username: usernameInput?.value || '',
|
||||
password: passwordInput?.value || '',
|
||||
};
|
||||
}
|
||||
|
||||
setCredentials(newCredentials);
|
||||
|
||||
// Notify RapiDoc of new credentials
|
||||
const rapiDoc = document.querySelector('rapi-doc');
|
||||
if (rapiDoc && 'setApiKey' in rapiDoc) {
|
||||
(rapiDoc as any).setApiKey(newCredentials.bearer || '');
|
||||
}
|
||||
|
||||
alert('Credentials saved for this session');
|
||||
});
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ import SidebarSearch from './components/sidebar-search.js';
|
|||
import { SidebarToggle } from './sidebar-toggle.js';
|
||||
import Theme from './theme.js';
|
||||
import ThemeSwitch from './theme-switch.js';
|
||||
import ApiAuthInput from './components/api-auth-input.ts';
|
||||
import ApiRapiDoc from './components/api-rapidoc.ts';
|
||||
import ApiToc from './components/api-toc.ts';
|
||||
import RapiDocMini from './components/rapidoc-mini.ts';
|
||||
|
|
@ -80,6 +81,7 @@ const componentRegistry = {
|
|||
'sidebar-toggle': SidebarToggle,
|
||||
theme: Theme,
|
||||
'theme-switch': ThemeSwitch,
|
||||
'api-auth-input': ApiAuthInput,
|
||||
'api-rapidoc': ApiRapiDoc,
|
||||
'api-toc': ApiToc,
|
||||
'rapidoc-mini': RapiDocMini,
|
||||
|
|
|
|||
|
|
@ -70,3 +70,90 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// API Auth Form - Custom credential input for operation pages
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
.api-auth-form {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: $g3-castle;
|
||||
border: 1px solid $g5-pepper;
|
||||
border-radius: 4px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.auth-form-description {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: $g9-mountain;
|
||||
}
|
||||
|
||||
.auth-field {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid $g5-pepper;
|
||||
border-radius: 3px;
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-save {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark theme overrides - using CSS selectors (no mixin in this codebase)
|
||||
[data-theme="dark"],
|
||||
html:has(link[title="dark-theme"]:not([disabled])) {
|
||||
.api-security-schemes {
|
||||
border-top-color: $grey25;
|
||||
|
||||
.security-scheme {
|
||||
background: $grey15;
|
||||
border-color: $grey25;
|
||||
}
|
||||
|
||||
.scheme-details {
|
||||
dt {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
|
||||
dd code {
|
||||
background: $grey20;
|
||||
}
|
||||
}
|
||||
|
||||
.scheme-description {
|
||||
border-top-color: $grey25;
|
||||
}
|
||||
}
|
||||
|
||||
.api-auth-form {
|
||||
background: $grey15;
|
||||
border-color: $grey25;
|
||||
|
||||
.auth-form-description {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
|
||||
.auth-field input {
|
||||
background: $grey20;
|
||||
border-color: $grey25;
|
||||
color: $g20-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@
|
|||
<link rel="alternate" type="application/json" href="{{ $specPathJSON | absURL }}" title="OpenAPI Specification (JSON)" />
|
||||
{{ end }}
|
||||
|
||||
{{/* Auth input component */}}
|
||||
<div class="api-auth-input-wrapper"
|
||||
data-component="api-auth-input"
|
||||
data-schemes="bearer,basic">
|
||||
</div>
|
||||
|
||||
{{/* Component container - TypeScript handles initialization */}}
|
||||
<div class="api-reference-wrapper api-reference-mini"
|
||||
data-component="rapidoc-mini"
|
||||
|
|
|
|||
Loading…
Reference in New Issue