|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>SAML DocGen Wireframe</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
.sidebar { |
|
scrollbar-width: thin; |
|
scrollbar-color: #4b5563 #1f2937; |
|
} |
|
.sidebar::-webkit-scrollbar { |
|
width: 6px; |
|
} |
|
.sidebar::-webkit-scrollbar-track { |
|
background: #1f2937; |
|
} |
|
.sidebar::-webkit-scrollbar-thumb { |
|
background-color: #4b5563; |
|
border-radius: 3px; |
|
} |
|
.code-block { |
|
font-family: 'Courier New', monospace; |
|
background-color: #1e293b; |
|
color: #f8fafc; |
|
} |
|
.tab-active { |
|
border-bottom: 2px solid #3b82f6; |
|
color: #3b82f6; |
|
} |
|
.diagram-placeholder { |
|
background: linear-gradient(135deg, #f3f4f6 25%, #e5e7eb 25%, #e5e7eb 50%, #f3f4f6 50%, #f3f4f6 75%, #e5e7eb 75%); |
|
background-size: 40px 40px; |
|
} |
|
.copy-success { |
|
animation: copyPulse 0.5s ease-in-out; |
|
} |
|
@keyframes copyPulse { |
|
0% { transform: scale(1); } |
|
50% { transform: scale(1.2); } |
|
100% { transform: scale(1); } |
|
} |
|
.slider-container { |
|
position: relative; |
|
width: 100%; |
|
margin: 20px 0; |
|
} |
|
.slider { |
|
-webkit-appearance: none; |
|
width: 100%; |
|
height: 8px; |
|
border-radius: 4px; |
|
background: #d1d5db; |
|
outline: none; |
|
} |
|
.slider::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
appearance: none; |
|
width: 20px; |
|
height: 20px; |
|
border-radius: 50%; |
|
background: #3b82f6; |
|
cursor: pointer; |
|
transition: all 0.2s; |
|
} |
|
.slider::-webkit-slider-thumb:hover { |
|
transform: scale(1.2); |
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2); |
|
} |
|
.slider-labels { |
|
display: flex; |
|
justify-content: space-between; |
|
margin-top: 8px; |
|
} |
|
.slider-label { |
|
font-size: 12px; |
|
color: #6b7280; |
|
} |
|
.slider-value { |
|
position: absolute; |
|
top: -30px; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
background: #3b82f6; |
|
color: white; |
|
padding: 4px 8px; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
} |
|
.slider-value:after { |
|
content: ''; |
|
position: absolute; |
|
top: 100%; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
border-width: 5px; |
|
border-style: solid; |
|
border-color: #3b82f6 transparent transparent transparent; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-50 text-gray-800"> |
|
<div class="flex h-screen overflow-hidden"> |
|
|
|
<div class="sidebar w-64 bg-gray-800 text-gray-200 flex flex-col h-full overflow-y-auto"> |
|
<div class="p-4 border-b border-gray-700"> |
|
<div class="flex items-center space-x-2"> |
|
<div class="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center"> |
|
<i class="fas fa-lock text-white"></i> |
|
</div> |
|
<h1 class="text-xl font-bold">SAML DocGen</h1> |
|
</div> |
|
<p class="text-xs text-gray-400 mt-1">v2.4.1</p> |
|
</div> |
|
|
|
<div class="p-4"> |
|
<div class="relative"> |
|
<input type="text" placeholder="Search components..." class="w-full bg-gray-700 text-gray-200 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
<i class="fas fa-search absolute right-3 top-3 text-gray-400"></i> |
|
</div> |
|
</div> |
|
|
|
<div class="flex-1 overflow-y-auto"> |
|
<div class="px-4 py-2"> |
|
<h3 class="text-xs uppercase tracking-wider text-gray-400 mb-2">Authentication</h3> |
|
<ul> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700 text-blue-400 bg-gray-700"> |
|
<i class="fas fa-key mr-2"></i> SAML Flow |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-fingerprint mr-2"></i> Single Sign-On |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-sign-out-alt mr-2"></i> Single Logout |
|
</a> |
|
</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="px-4 py-2 mt-4"> |
|
<h3 class="text-xs uppercase tracking-wider text-gray-400 mb-2">Configuration</h3> |
|
<ul> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-id-card mr-2"></i> Identity Providers |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px极3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-server mr-2"></i> Service Providers |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-certificate mr-2"></i> Certificates |
|
</a> |
|
</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="px-4 py-2 mt-4"> |
|
<h3 class="text-xs uppercase tracking-wider text-gray-400 mb-2">Advanced</h3> |
|
<ul> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-code mr-2"></i> Custom Claims |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-shield-alt mr-2"></i> Security Policies |
|
</a> |
|
</li> |
|
<li class="mb-1"> |
|
<a href="#" class="block px-3 py-2 rounded-md hover:bg-gray-700"> |
|
<i class="fas fa-plug mr-2"></i> Webhooks |
|
</a> |
|
</li> |
|
</ul> |
|
</div> |
|
</div> |
|
|
|
<div class="p-4 border-t border-gray-700"> |
|
<div class="flex items-center"> |
|
<div class="w-8 h-8 rounded-full bg-gray-600 flex items-center justify-center mr-2"> |
|
<i class="fas fa-user"></i> |
|
</div> |
|
<div> |
|
<p class="text-sm font-medium">Admin User</p> |
|
<p class="text-xs text-gray-400">admin@saml.co.za</p> |
|
</div> |
|
<button class="ml-auto text-gray-400 hover:text-white"> |
|
<i class="fas fa-cog"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 flex flex-col overflow-hidden"> |
|
|
|
<header class="bg-white border-b border-gray-200 p-4 flex items-center justify-between"> |
|
<div> |
|
<h2 class="text-xl font-semibold">SAML Authentication Flow</h2> |
|
<p class="text-sm text-gray-500">Web Browser SSO Profile (SAML 2.0)</p> |
|
</div> |
|
<div class="flex space-x-3"> |
|
<div class="relative group"> |
|
<button class="px-4 py-2 bg-gray-100 rounded-md hover:bg-gray-200 flex items-center"> |
|
<i class="fas fa-code-branch mr-2 text-gray-600"></i> |
|
<span>Version 2.4.1</span> |
|
<i class="fas fa-chevron-down ml-2 text-xs text-gray-500"></i> |
|
</button> |
|
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 w-48 z-10 border border-gray-200"> |
|
<a href="#" class="block px-4 py-2 text-sm hover:bg-gray-100">Version 2.4.1 (current)</a> |
|
<a href="#" class="block px-4 py-2 text-sm hover:bg-gray-100">Version 2.3.0</a> |
|
<a href="#" class="block px-4 py-2 text-sm hover:bg-gray-100">Version 2.2.5</a> |
|
</div> |
|
</div> |
|
<button class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"> |
|
<i class="fas fa-download mr-2"></i> |
|
<span>Export</span> |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<div class="flex-1 overflow-y-auto p-6"> |
|
<div class="max-w-5xl mx-auto"> |
|
|
|
<div class="border-b border-gray-200 mb-6"> |
|
<nav class="flex space-x-8"> |
|
<button class="tab-active py-4 px-1 font-medium">Documentation</button> |
|
<button class="py-4 px-1 font-medium text-gray-500 hover:text-gray-700">Examples</button> |
|
<button class="py-4 px-1 font-medium text-gray-500 hover:text-gray-700">Configuration</button> |
|
<button class="py-4 px-1 font-medium text-gray-500 hover:text-gray-700">Troubleshooting</button> |
|
</nav> |
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 mb-6"> |
|
<div class="flex items-center justify-between mb-4"> |
|
<h3 class="text-lg font-semibold">SAML Authentication Flow</h3> |
|
<div class="flex space-x-2"> |
|
<button class="px-3 py-1 bg-gray-100 rounded-md text-sm hover:bg-gray-200"> |
|
<i class="fas fa-bookmark mr-1"></i> Bookmark |
|
</button> |
|
<button class="px-3 py-1 bg-gray-100 rounded-md text-sm hover:bg-gray-200"> |
|
<i class="fas fa-share-alt mr-1"></i> Share |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<p class="text-gray-600 mb-6"> |
|
The SAML authentication flow allows users to authenticate using their identity provider (IdP) credentials. |
|
This component implements the standard SAML 2.0 Web Browser SSO Profile with support for both SP-initiated and IdP-initiated flows. |
|
</p> |
|
|
|
<div class="mb-8"> |
|
<div class="flex items-center justify-between mb-3"> |
|
<h4 class="font-medium">Sequence Diagram</h4> |
|
<button class="text-sm text-blue-600 hover:text-blue-800"> |
|
<i class="fas fa-expand mr-1"></i> Fullscreen |
|
</button> |
|
</div> |
|
<div class="diagram-placeholder p-4 rounded-md border border-gray-200"> |
|
<div class="flex justify-center items-center h-64"> |
|
<div class="text-center"> |
|
<i class="fas fa-project-diagram text-4xl text-gray-300 mb-2"></i> |
|
<p class="text-gray-400">SAML authentication sequence diagram</p> |
|
<button class="mt极2 px-3 py-1 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700"> |
|
Generate Diagram |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mb-8"> |
|
<h4 class="font-medium mb-3">Security Level Configuration</h4> |
|
<div class="slider-container"> |
|
<div class="slider-value" id="slider-value">Medium</div> |
|
<input type="range" min="0" max="2" value="1" class="slider" id="security-level" step="1"> |
|
<div class="slider-labels"> |
|
<span class="slider-label">Low</span> |
|
<span class="slider-label">Medium</span> |
|
<span class="slider-label">High</span> |
|
</div> |
|
</div> |
|
<div class="mt-4 p-4 bg-gray-50 rounded-md"> |
|
<h5 class="font-medium mb-2">Current Security Settings:</h5> |
|
<ul class="list-disc pl-5 text-sm text-gray-600" id="security-settings"> |
|
<li>256-bit encryption</li> |
|
<li>Password-protected transport</li> |
|
<li>Session timeout: 30 minutes</li> |
|
</ul> |
|
</div> |
|
</div> |
|
|
|
<div class="mb-8"> |
|
<h4 class="font-medium mb-3">Configuration Parameters</h4> |
|
<div class="overflow-x-auto"> |
|
<table class="min-w-full divide-y divide-gray-200"> |
|
<thead class="bg-gray-50"> |
|
<tr> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Parameter</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Required</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Default</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th> |
|
</tr> |
|
</thead> |
|
<tbody class="bg-white divide-y divide-gray-200"> |
|
<tr> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">idpMetadataUrl</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">string</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Yes</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">-</td> |
|
<td class="px-6 py-4 text-sm text-gray-500">URL to the IdP metadata XML</td> |
|
</tr> |
|
<tr class="bg-gray-50"> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">spEntityId</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">string</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Yes</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">-</td> |
|
<td class="px-6 py-4 text-sm text-gray-500">Service Provider entity ID</td> |
|
</tr> |
|
<tr> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">assertEndpoint</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">string</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Yes</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">-</td> |
|
<td class="px-6 py-4 text-sm text-gray-500">Assertion consumer service URL</td> |
|
</tr> |
|
<tr class="bg-gray-50"> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">nameIdFormat</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">string</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">No</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</td> |
|
<td class="px-6 py-4 text-sm text-gray-500">Format for NameID</td> |
|
</tr> |
|
<tr> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">authnContext</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">string</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">No</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</td> |
|
<td class="px-6 py-4 text-sm text-gray-500">Authentication context class</td> |
|
</tr> |
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
|
|
<div class="mb-8"> |
|
<h4 class="font-medium mb-3">Example Implementation</h4> |
|
<div class="mb-4"> |
|
<div class="flex justify-between items-center bg-gray-800 text-gray-200 px-4 py-2 rounded-t-md"> |
|
<div class="极flex items-center"> |
|
<i class="fab fa-js mr-2"></i> |
|
<span>JavaScript</span> |
|
</div> |
|
<button class="text-gray-400 hover:text-white copy-btn"> |
|
<i class="far fa-copy"></i> |
|
</button> |
|
</div> |
|
<pre class="code-block p-4 rounded-b-md overflow-x-auto"><code>const samlAuth = new SAMLAuthentication({ |
|
// Required configuration |
|
idpMetadataUrl: 'https://idp.example.com/metadata.xml', |
|
spEntityId: 'urn:example:sp', |
|
assertEndpoint: 'https://your-app.com/saml/acs', |
|
|
|
// Optional configuration |
|
name极IdFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', |
|
authnContext: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', |
|
forceAuthn: false, |
|
allowCreate: true, |
|
requestBinding: 'HTTP-Redirect', |
|
responseBinding: 'HTTP-POST' |
|
}); |
|
|
|
// Initiate SP-initiated login |
|
app.get('/login', (req, res) => { |
|
samlAuth.login(req, res, { |
|
// Optional parameters |
|
RelayState: '/dashboard', |
|
AuthnRequestExtensions: '<my:CustomAttribute>value</my:CustomAttribute>' |
|
}); |
|
}); |
|
|
|
// Handle assertion response |
|
app.post('/saml/acs', (req, res) => { |
|
samlAuth.handleAssertion(req, res) |
|
.then(user => { |
|
// User authenticated successfully |
|
req.session.user = user; |
|
res.redirect(req.body.RelayState || '/'); |
|
}) |
|
.catch(err => { |
|
// Handle authentication failure |
|
console.error('SAML authentication failed:', err); |
|
res.status(401).render('error', { |
|
message: 'Authentication failed', |
|
details: err.message |
|
}); |
|
}); |
|
}); |
|
|
|
// Metadata endpoint |
|
app.get('/saml/metadata', (req, res) => { |
|
res.type('application/xml'); |
|
res.send(samlAuth.generateMetadata()); |
|
});</code></pre> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
<div> |
|
<div class="flex justify-between items-center bg-gray-800 text-gray-200 px-4 py-2 rounded-t-md"> |
|
<div class="flex items-center"> |
|
<i class="fab fa-java mr-2"></i> |
|
<span>Java</span> |
|
</div> |
|
<button class="text-gray-400 hover:text-white copy-btn"> |
|
<i class="far fa-copy"></i> |
|
</button> |
|
</div> |
|
<pre class="code-block p-4 rounded-b-md overflow-x-auto"><code>@Configuration |
|
@EnableWebSecurity |
|
public class SecurityConfig extends WebSecurityConfigurerAdapter { |
|
|
|
@Value("${saml.idp.metadata}") |
|
private String idpMetadataUrl; |
|
|
|
@Bean |
|
public SAMLAuthenticationProvider samlAuthenticationProvider() { |
|
return new SAMLAuthenticationProvider(); |
|
} |
|
|
|
@Bean |
|
public SAMLMetadataFilter metadataFilter() { |
|
return new SAMLMetadataFilter(); |
|
} |
|
|
|
@Override |
|
protected void configure(HttpSecurity http) throws Exception { |
|
http |
|
.authorizeRequests() |
|
.antMatchers("/saml/**").permitAll() |
|
.anyRequest().authenticated() |
|
.and() |
|
.apply(new SAMLConfigurer()) |
|
.idpMetadataUrl(idpMetadataUrl) |
|
.spEntityId("urn:example:sp") |
|
.assertionConsumerServiceUrl("/saml/acs") |
|
.and() |
|
.addFilterBefore(metadataFilter(), |
|
UsernamePasswordAuthenticationFilter.class); |
|
} |
|
}</code></pre> |
|
</div> |
|
<div> |
|
<div class="flex justify-between items-center bg-gray-800 text-gray-200 px-4 py-2 rounded-t-md"> |
|
<div class="flex items-center"> |
|
<i class="fab fa-python mr-2"></i> |
|
<span>Python</span> |
|
</div> |
|
<button class="text-gray-400 hover:text-white copy-btn"> |
|
<i class="far fa-copy"></i> |
|
</button> |
|
</div> |
|
<pre class="code-block p-4 rounded-b-md overflow-x-auto"><code>from flask import Flask, request, redirect, session |
|
from flask_saml import SAMLAuthentication |
|
|
|
app = Flask(__name__) |
|
app.secret_key = 'your-secret-key' |
|
|
|
saml_auth = SAMLAuthentication( |
|
app, |
|
idp_metadata_url='https://idp.example.com/metadata.xml', |
|
sp_entity_id='urn:example:sp', |
|
acs_url='/saml/acs' |
|
) |
|
|
|
@app.route('/login') |
|
def login(): |
|
return saml_auth.login(relay_state='/dashboard') |
|
|
|
@app.route('/saml/acs', methods=['POST']) |
|
def acs(): |
|
user = saml_auth.process_response() |
|
session['user'] = user |
|
return redirect(request.form.get('RelayState', '/')) |
|
|
|
@app.route('/saml/metadata') |
|
def metadata(): |
|
return saml_auth.generate_metadata()</code></pre> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<h4 class="font-medium mb-3">Related Components</h4> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
<div class="border border-gray-200 rounded-md p-4 hover:border-blue-200 hover:bg-blue-50 transition-colors cursor-pointer"> |
|
<div class="flex items-center mb-2"> |
|
<div class="w-8 h-8 bg-blue-100 rounded-md flex items-center justify-center mr-2"> |
|
<i class="fas fa-sign-out-alt text-blue-600"></i> |
|
</div> |
|
<h5 class="font-medium">Single Logout</h5> |
|
</div> |
|
<p class="text-sm text-gray-600">Implement SAML Single Logout (SLO) for session termination</p> |
|
</div> |
|
<div class="border border-gray-200 rounded-md p-4 hover:border-purple-200 hover:bg-purple-50 transition-colors cursor-pointer"> |
|
<div class="flex items-center mb-2"> |
|
<div class="w-8 h-8 bg-purple-100 rounded-md flex items-center justify-center mr-2"> |
|
<i class="fas fa-id-badge text-purple-600"></i> |
|
</div> |
|
<h5 class="font-medium">Attribute Mapping</h5> |
|
</div> |
|
<p class="text-sm text-gray-600">Map SAML attributes to user profile fields</p> |
|
</div> |
|
<div class="border border-gray-200 rounded-md p-4 hover:border-green-200 hover:bg-green-50 transition-colors cursor-pointer"> |
|
<div class="flex items-center mb-2"> |
|
<div class="w-8 h-8 bg-green-100 rounded-md flex items-center justify-center mr-2"> |
|
<i class="fas fa-shield-alt text-green-600"></i> |
|
</div> |
|
<h5 class="font-medium">Security Policies</h5> |
|
</div> |
|
<p class="text-sm text-gray-600">Configure security policies for SAML assertions</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6"> |
|
<div class="flex items-center justify-between mb-4"> |
|
<h3 class="text-lg font-semibold">Feedback</h3> |
|
<div class="flex space-x-2"> |
|
<button id="helpful-btn" class="px-3 py-1 bg-gray-100 rounded-md text-sm hover:bg-gray-200"> |
|
<i class="far fa-thumbs-up mr-1"></i> Helpful |
|
</button> |
|
<button id="improve-btn" class="px-3 py-1 bg-gray-100 rounded-md text-sm hover:bg-gray-200"> |
|
<i class="far fa-thumbs-down mr-1"></i> Needs improvement |
|
</button> |
|
</div> |
|
</div> |
|
<textarea id="feedback-text" class="w-full border border-gray-300 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-blue-500" rows="3" placeholder="Have feedback about this component? Let us know..."></textarea> |
|
<div class="flex justify-end mt-3"> |
|
<button id="submit-feedback" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"> |
|
Submit Feedback |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const tabs = document.querySelectorAll('nav button'); |
|
tabs.forEach(tab => { |
|
tab.addEventListener('click', () => { |
|
tabs.forEach(t => { |
|
t.classList.remove('tab-active', 'text-blue-600'); |
|
t.classList.add('text-gray-500', 'hover:text-gray-700'); |
|
}); |
|
|
|
tab.classList.add('tab-active', 'text-blue-600'); |
|
tab.classList.remove('text-gray-500', 'hover:text-gray-700'); |
|
|
|
|
|
console.log(`Switched to tab: ${tab.textContent.trim()}`); |
|
}); |
|
}); |
|
|
|
|
|
const copyButtons = document.querySelectorAll('.copy-btn'); |
|
copyButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
const codeBlock = button.closest('div').nextElementSibling; |
|
const code = codeBlock.querySelector('code').textContent; |
|
|
|
navigator.clipboard.writeText(code).then(() => { |
|
const icon = button.querySelector('i'); |
|
icon.className = 'fas fa-check text-green-400 copy-success'; |
|
|
|
setTimeout(() => { |
|
icon.className = 'far fa-copy'; |
|
}, 2000); |
|
}); |
|
}); |
|
}); |
|
|
|
|
|
const helpfulBtn = document.getElementById('helpful-btn'); |
|
const improveBtn = document.getElementById('improve-btn'); |
|
const feedbackText = document.getElementById('feedback-text'); |
|
const submitFeedback = document.getElementById('submit-feedback'); |
|
|
|
let feedbackType = null; |
|
|
|
helpfulBtn.addEventListener('click', () => { |
|
feedbackType = 'helpful'; |
|
helpfulBtn.classList.add('bg-blue-100', 'text-blue-800'); |
|
improveBtn.classList.remove('bg-blue-100', 'text-blue-800'); |
|
}); |
|
|
|
improveBtn.addEventListener('click', () => { |
|
feedbackType = 'needs_improvement'; |
|
improveBtn.classList.add('bg-blue-100', 'text-blue-800'); |
|
helpfulBtn.classList.remove('bg-blue-100', 'text-blue-800'); |
|
}); |
|
|
|
submitFeedback.addEventListener('click', () => { |
|
if (!feedbackType) { |
|
alert('Please select feedback type first'); |
|
return; |
|
} |
|
|
|
const feedback = feedbackText.value.trim(); |
|
if (!feedback && feedbackType === 'needs_improvement') { |
|
alert('Please provide details about what needs improvement'); |
|
return; |
|
} |
|
|
|
|
|
console.log('Feedback submitted:', { |
|
type: feedbackType, |
|
content: feedback, |
|
component: 'SAML Authentication Flow', |
|
timestamp: new Date().toISOString() |
|
}); |
|
|
|
alert('Thank you for your feedback!'); |
|
feedbackText.value = ''; |
|
helpfulBtn.classList.remove('bg-blue-100', 'text-blue-800'); |
|
improveBtn.classList.remove('bg-blue-100', 'text-blue-800'); |
|
feedbackType = null; |
|
}); |
|
|
|
|
|
const versionBtn = document.querySelector('.group button'); |
|
versionBtn.addEventListener('click', (e) => { |
|
e.stopPropagation(); |
|
const dropdown = document.querySelector('.group .absolute'); |
|
dropdown.classList.toggle('hidden'); |
|
}); |
|
|
|
|
|
document.addEventListener('click', () => { |
|
const dropdown = document.querySelector('.group .absolute'); |
|
if (dropdown && !dropdown.classList.contains('hidden')) { |
|
dropdown.classList.add('hidden'); |
|
} |
|
}); |
|
|
|
|
|
const securitySlider = document.getElementById('security-level'); |
|
const sliderValue = document.getElementById('slider-value'); |
|
const securitySettings = document.getElementById('security-settings'); |
|
|
|
|
|
updateSliderValue(securitySlider.value); |
|
|
|
securitySlider.addEventListener('input', function() { |
|
updateSliderValue(this.value); |
|
}); |
|
|
|
function updateSliderValue(value) { |
|
const numericValue = parseInt(value); |
|
let level = ''; |
|
let settings = []; |
|
|
|
switch(numericValue) { |
|
case 0: |
|
level = 'Low'; |
|
settings = [ |
|
'128-bit encryption', |
|
'Basic authentication', |
|
'Session timeout: 60 minutes' |
|
]; |
|
break; |
|
case 1: |
|
level = 'Medium'; |
|
settings = [ |
|
'256-bit encryption', |
|
'Password-protected transport', |
|
'Session timeout: 30 minutes' |
|
]; |
|
break; |
|
case 2: |
|
level = 'High'; |
|
settings = [ |
|
'512-bit encryption', |
|
'Multi-factor authentication', |
|
'Session timeout: 15 minutes' |
|
]; |
|
break; |
|
} |
|
|
|
sliderValue.textContent = level; |
|
|
|
|
|
securitySettings.innerHTML = ''; |
|
settings.forEach(setting => { |
|
const li = document.createElement('li'); |
|
li.textContent = setting; |
|
securitySettings.appendChild(li); |
|
}); |
|
} |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=sudzdpn/wireframe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |